Tuesday, March 25, 2014

Il modulo ctypes e la libreria libswe.so

Siccome la mia class SVG2Tk è ancora un po' indietro di cottura, apro una piccola parentesi sull'accesso diretto alle librerie condivise scritte in linguaggio C.

La libreria pyswisseph, che ho usato nei primi post, è un lavoro splendido e ci permette di effettuare chiamate alla libreria con istruzioni in Python nativo. Se pero' volessimo usare la libreria originaria, installata dal software center di Ubuntu, dovremmo confrontarci con un problema: non è scritta in Python ma in C e compilata verosimilmente con gcc, il compilatore GNU. La stessa pyswisseph è un insieme di funzioni wrapper (involucro) che fanno da ponte fra i due linguaggi. Anche noi, però, possiamo farlo direttamente dal nostro codice.

Tra le librerie integrate nell'installazione di Python c'è un modulo realizzato appositamente per chiamare una libreria in C, a patto che si conoscano le specifiche della libreria, i nomi delle funzioni e i parametri in input e in output.

Fortunatamente la libreria swisseph è molto ben documentata e con un po' di sforzo possiamo effettuare delle chiamate come se utilizzassimo una libreria pitonica. Vediamo come, prendendo a spunto un post del curatore delle pyswisseph Stanislas Marquis link.

Per prima cosa apriamo IDLE e scriviamo l'import di ctypes e di datetime. A questo punto creiamo una variabile shared_lib che costituirà il link alla libreria, attraverso il metodo CDLL di ctypes (o, in alternativa, il metodo cdll.LoadLibrary, sovrapponibile al precedente). L'installazione di swisseph da ubuntu software center mette la libreria libswe.so in una specifica posizione sul disco. Qualora volessimo spostarla nella stessa directory del sorgente Python sarà necessario cambiare il path della libreria stessa. Aggiungiamo inoltre due dizionari per i nomi dei pianeti e dei segni zodiacali.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding: utf-8 -*-
import datetime
import ctypes as ct 

shared_lib = ct.CDLL('/usr/lib/x86_64-linux-gnu/libswe.so')

pianeti = {0:'Sole',
           1:'Luna',
           2:'Mercurio',
           3:'Venere',
           4:'Marte',
           5:'Giove',
           6:'Saturno',
           7:'Urano',
           8:'Nettuno',
           9:'Plutone',
           10:'Nodo lunare medio',
           11:'Nodo lunare vero'}

segni = {0:'Ariete',
         1:'Toro',
         2:'Gemelli',
         3:'Cancro',
         4:'Leone',
         5:'Vergine',
         6:'Bilancia',
         7:'Scorpione',
         8:'Sagittario',
         9:'Capricorno',
         10:'Acquario',
         11:'Pesci'}

Della libreria swisseph, in questo programma, uso solo due funzioni, una per il calcolo della data giuliana, l'altra per il calcolo della posizione dei pianeti. Vediamo la prima.


1
2
3
4
5
6
7
_julday=shared_lib.swe_julday
_julday.argtypes=[ct.c_int, ct.c_int, ct.c_int, ct.c_double, ct.c_int]
_julday.restype=ct.c_double

def julday(year, month, day, hour, gregflag=1):
    return _julday(ct.c_int(year), ct.c_int(month), ct.c_int(day),
                   ct.c_double(hour), ct.c_int(gregflag))

La prima parte è molto semplice. La libreria ctypes consente di accedere alla funzione nascosta nella shared library swe_julday definendo prima gli argtypes, cioè le tipologie di variabili da usare come parametri di input, poi il restype, cioè il tipo di variabile di ritorno dalla funzione (ricordiamo che il linguaggio C non consente la tipizzazione dinamica, cioè l'assegnazione arbitraria in runtime di un tipo all'atto della creazione della variabile. Essendo un linguaggio compilato, richiede, ovviamente, la tipizzazione di tipo statico).

La chiamata da codice Python risulta molto naturale, in questo caso i parametri passati alla funzione julday vengono trasferiti ai tipi definiti per l'interfaccia al linguaggio C. La funzione restituisce una variabile double, in Python corrisponde a una float e fornisce il valore della data giuliana calcolata da data e ora.

Un po' più complessa risulta, invece, la seconda funzione, che calcola le posizioni planetarie. Di seguito un estratto della guida Programming Interface to the Swiss Ephemeris:

2.1. The call parameters
swe_calc_ut() was introduced with Swisseph version 1.60 and makes planetary calculations a bit simpler. For the steps required, see the chapter  The programming steps to get a planet’s position.
swe_calc_ut() and swe_calc() work exactly the same way except that swe_calc() requires Ephemeris Time( more accurate: Dynamical Time ) as a parameter whereas swe_calc_ut() expects Universal Time. For common astrological calculations, you will only need swe_calc_ut() and will not have to think anymore about the conversion between Universal Time and Ephemeris Time.
swe_calc_ut() and swe_calc() compute positions of planets, asteroids, lunar nodes and apogees. They are defined as follows:
 
int swe_calc_ut ( double tjd_ut, int ipl, int iflag, double* xx, char* serr),
where
tjd_ut     =Julian day, Universal Time
ipl       =body number
iflag    =a 32 bit integer containing bit flags that indicate what kind of computation is wanted
xx       =array of 6 doubles for longitude, latitude, distance, speed in long., speed in lat., and speed in dist.
serr[256] =character string to return error messages in case of error.
 
and
int swe_calc(double tjd_et, int ipl, int iflag, double *xx, char *serr),
same but
tjd_et     =     Julian day, Ephemeris time,  where tjd_et = tjd_ut + swe_deltat(tjd_ut)

Nella dichiarazione della funzione swe_calc compaiono due puntatori (in Python non esistono, o meglio le variabili Python sono dei puntatori, ma non dilunghiamoci), cioè gli indirizzi di memoria nei quali possiamo trovare le variabili xx (che contiene un array di 6 doubles, in Python diremmo una tupla di 6 float) e serr (per l'eventuale messaggio di errore).

I tipi di variabili ctypes possono essere inizializzati nel codice Python con modalità differenti.

Per i tipi più semplici, ctypes.c_int(42) ctypes.c_float(3.1415), ad esempio, il valore tra parentesi viene conservato in una locazione di memoria da cui possono essere recuperati con l'espressione .value. Per esempio:

>>> a = ctypes.c_int(42)
>>> print a
c_int(42)
>>> print a.value
42
>>> b = ctypes.c_float(3.1415)
>>> print b
c_float(3.1414999961853027)
>>> print b.value
3.14149999619

Vediamo ora come si puo' effettuare la traduzione in Python della funzione swe_calc di swisseph utilizzando ctypes.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
_calc = shared_lib.swe_calc
_calc.argtypes = [ct.c_double, ct.c_int, ct.c_int, ct.POINTER(ct.c_double*6), ct.c_char_p]
_calc.restype = ct.c_int

def calc(julday, planet, flag = 0):

    xx = (ct.c_double * 6)()
    err = ct.c_char_p('')
    if _calc(ct.c_double(julday), ct.c_int(planet), ct.c_int(flag),
             ct.byref(xx), err) == 0:
        return [x for x in xx]
    return err.value

Come potete vedere, il tipo POINTER viene usato per tradurre il puntatore ad una variabile (preceduta da *) di C. Per la variabile serr si usa il tipo predefinito ctypes.c_char_p che è esso stesso un puntatore. La funzione accetta come parametri un double che riprende il risultato del calcolo della data giuliana, una variabile c_int per il numero di posizione del pianeta nella serie e infine il tipo c_int(flag), inizializzato a 0 perchè "If no bits are set, i.e. if iflag == 0, swe_calc() computes what common astrological ephemerides (as available in book shops) supply". Se il valore di ritorno di _calc è zero, cioè non viene resituito un errore, l'array di 6 double va a popolare una list di 6 float contenente vari elementi relativi al pianeta considerato. Di questi ci interessa solo l'elemento con indice 0, riferito alla longitudine in gradi. La lista viene generata con una list comprehension [x for x in xx].

L'ultima parte del programma contiene istruzioni per l'uso delle funzioni C che abbiamo creato in precedenza.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def d2hm(x):
    lon30 = x % 30
    segno = int(x) / 30
    grado = int(lon30)
    minuto = int((lon30-grado)*60)
    return (grado, segno, minuto)


adesso = datetime.datetime.now()
in_questo_momento = julday(adesso.year,
       adesso.month,
       adesso.day,
       adesso.hour + adesso.minute/60.0 + adesso.second/3600.0)

for i in range(0,12):
    longitudine = calc(in_questo_momento,i)[0]
    pos = d2hm(longitudine)
    print "%18s %2d° %10s %2d'" % (pianeti[i], pos[0], segni[pos[1]], pos[2])

Lanciando il programma si ottiene il seguente risultato:

              Sole  4°     Ariete 49'
              Luna 24° Capricorno 33'
          Mercurio  9°      Pesci 42'
            Venere 18°   Acquario 18'
             Marte 23°   Bilancia 56'
             Giove 11°     Cancro  1'
           Saturno 22°  Scorpione 53'
             Urano 12°     Ariete  1'
           Nettuno  6°      Pesci  8'
           Plutone 13° Capricorno 28'
 Nodo lunare medio 29°   Bilancia 50'
  Nodo lunare vero 28°   Bilancia 36'

Riferito ad oggi 25 marzo alle 13.37 tempo di Greenwich

Spero che questo post sia stato utile per capire come interfacciarsi ad una shared library compilata in linguaggio C. Ulteriori informazioni sull'uso del modulo ctypes sono rintracciabili alla pagina http://docs.python.org/2/library/ctypes.html#

Se volete creare voi stessi la libreria libswe.so, scaricate il file a questo link. Decomprimetelo e lanciate l'istruzione make, che crea i file oggetto. Quindi create la libreria condivisa, nella stessa directory, con l'istruzione gcc -g -O9 -Wall -fPIC -lm -shared -o libswe.so swedate.o swehouse.o swejpl.o swemmoon.o swemplan.o swepcalc.o sweph.o swepdate.o swephlib.o swecl.o swehel.o. La libreria libswe.so comparirà nella directory. Copiatela dove avete collocato il sorgente .py di cui a questo post. Modificate il riferimento alla libreria nel vostro codice in shared_lib = ct.CDLL('libswe.so') e lanciate in esecuzione il codice python, funzionerà perfettamente.

Sunday, March 16, 2014

Quarto intermezzo - L'indice di concentrazione planetaria

Si deve ad André Barbault, insigne astrologo francese, l'introduzione di un indice sintetico che rappresenta il grado di maggiore o minore allontanamento reciproco dei pianeti, che, prescindendo dalle metodologie tradizionali dell'astrologia, basate sulla posizione dei pianeti nei segni e sugli aspetti, cioè distanze angolari discrete dotate di tolleranze contenute nell'ordine di qualche grado, consentirebbe la lettura degli eventi collettivi (astrologia mondiale) secondo cicli complessi che variano tra minimi e massimi nell'arco di giorni, mesi, anni o addirittura secoli. Per i necessari approfondimenti rimando all'opera dell'autore citato, in particolare a:

  1. A. Barbault. Il pronostico sperimentale in astrologia. Mursia ed. 1979
  2. A. Barbault. Astrologia mondiale (trad. Clara Negri). Armenia 1980

Per il calcolo dell'indice ho pensato di fornire un piccolo contributo personale ai lettori esperti di linguaggio Python, appoggiandomi ad alcune librerie esterne per Python:

La prima è pyephem di Brandon Rhodes, una libreria astronomica (non astrologica come swisseph di Astrodienst)

la seconda è pylab, dedicata alla grafica matematica, che fornisce una sintesi di numpy, scipy e matplotlib per fornire un ambiente simile all'ambiente MATLAB, realizzato per il linguaggio C.

Come sempre fornisco un listato (quick and dirty) per il calcolo dell'indice. Ho dato un orizzonte temporale di 50 anni, ma, ovviamente, chi ha già familiarità con Python puo' divertirsi a cambiare data di inizio, lunghezza del periodo, e altri parametri. Le librerie datetime e time sono incluse nell'installazione Python di base, quindi vanno solo importate. Per pylab nel repository di Ubuntu trovate il pacchetto python-matplotlib. Numpy può essere installata attraverso pip o dai repository di Ubuntu senza particolari difficoltà.

Ok, vi passo il codice per l'indice di concentrazione planetaria, considerando i pianeti lenti da Giove a Plutone. Nel grafico compare anche (quasi incomprensibile, lo so) la posizione all'inizio di ogni anno dei cinque pianeti considerati, nell'intervallo 0-360 gradi. Puo' aiutare a capire almeno quando si realizzano delle congiunzioni importanti.


import  pylab ,  numpy 
import  ephem 
import  time ,  datetime

planets = { 0 :ephem.Sun, 1 :ephem.Moon, 2 :ephem.Mercury, 3 :ephem.Venus,
            4 :ephem.Mars, 5 :ephem.Jupiter, 6 :ephem.Saturn, 7 :ephem.Uranus,
            8 :ephem.Neptune, 9 :ephem.Pluto}

def  longitude (x):
     return ephem.Ecliptic (x)

year =  1990 
month =  1 
day =  1 
hour =  0 
minute =  0 
time_span =  50

initial_date = datetime.datetime (year, month, day, hour, minute)

x = []
y = []
jup = []
sat = []
ura = []
nep = []
plu = []

for i in  range ( 0 , time_span):
     date = datetime.datetime (year + i, month, day, hour, minute)
     b = []
     icp =  0 
     for key in  range ( 5 , 10 ):
          a = planets[key](date, epoch = date)
          b.append(ephem.degrees(ephem.Ecliptic(a).lon) / ephem.pi * 180 )
     for j in  range ( 0 , len (b)):
          for k in  range (j, len (b)):
               _icp = (b [k] - b [j])
               if _icp <  0 :
                    _icp +=  360 
               if _icp >  180 :
                    _icp =  360 - _icp
               icp += _icp
     x.append (i + year)
     y.append (icp)
     jup.append (b[0])
     sat.append (b[1])
     ura.append (b[2])
     nep.append (b[3])
     plu.append (b[4])

pylab.figure(1,figsize =(15 , 10 ))
pylab.title ( 'Index planetary concentration' )
pylab.xlabel ( 'year' )
pylab.ylabel ( 'ICP' )
pylab.xticks (numpy.linspace (year, year + time_span, 50 , endpoint = True ), rotation = - 90 )
pylab.plot (x, y, 'r' , label = 'ICP' )
pylab.plot (x, jup, 'bo' , label = 'jupiter' )
pylab.plot (x, sat, 'g ^' , label = 'saturn' )
pylab.plot (x, ura, 'c.' , label = 'uranus' )
pylab.plot (x, nep, 'm,' , label = 'neptune' )
pylab.plot (x, plu, 'k.' , label = 'bar' )

pylab.grid (axis = 'Both' )
pylab.legend (loc = 'best' )
pylab.show ()

A fra qualche giorno con l'ultima puntata di SVG2Tk.

Friday, March 14, 2014

XML - ricerca ricorsiva rivisitata

Le cose non vanno sempre lisce quando si esplora un protocollo. Nel caso dei file SVG il procedimento seguito finora puo' non funzionare (anzi non funziona quasi mai) perchè il protocollo SVG è lontano dall'avere sempre gli stessi contenuti e la stessa disposizione degli stessi. L'unica cosa certa è che il file SVG è un file XML, quindi composto da tag nidificate, che i dati sono prevalentemente negli attributi piuttosto che in testo libero e che dobbiamo quindi trovare una modalità di esplorazione, sempre ricorsiva, che in primo luogo ci dia la certezza di estrarre tutti i tag e relativi attributi e quindi ci consenta la comparazione dei tag con modelli tipo e relativi pattern di estrazione con le regexp.

Ho quindi riscritto (e allego di seguito) il prototipo della funzione xmlrecur per adattarsi a qualsiasi file xml o SVG di cui vogliamo fare successive conversioni.


#|/usr/bin/env python
from xml.etree import ElementTree as ET

def xmlrecur(x):
    print '<elemento',x,'>'
    print '<tag>', x.tag
    print '<attributo>',x.attrib
    for key in x.attrib:
        print 'singolo attributo', key, '-', x.attrib[key]
    print '<testo>',x.text
    print '-'*80
        
    for i in x:
            xmlrecur(i)

tree = ET.parse('xxx.xml')
root = tree.getroot()
xmlrecur(root)

Vi prego di notare che la proprietà attrib è un dizionario, di cui si puo' fare la scansione. Su questa particolarità concentreremo l'analisi per l'identificazione delle word caratteristiche di nostro interesse.

Ancora qualche giorno per finire il tutto. A presto.

Friday, March 7, 2014

Ulteriore miglioramento del codice

Avevo annunciato l'intenzione di eliminare, dalla classe SVG2Tk, l'uso di file di appoggio esterni.

Nel file che allego in questo post mostro come si puo' fare. Un difetto fondamentale della classe, come è stata progettata finora, è la pesantezza dell'analisi del file SVG, la necessità di riversare le informazioni ottenuta in file esterni, che vengono scritti e riletti senza che ce ne sia un'effettiva necessità. Un ulteriore miglioramento puo' essere realizzato con l'uso dei generatori, argomento tutt'altro che facile da capire, ma che cercherò di rendere nel modo più semplice possibile.

In primo luogo, disponendo di una metodo ricorsivo per l'identificazione degli attributi fondamentali del file SVG, utili al nostro lavoro di traduzione, possiamo cercare di fare in un'unica passata la lettura e la trascrizione dei punti di tracciato, senza salvataggi intermedi e senza creare lunghe liste in memoria, che, nonostante le notevoli migliorie delle tecnologie attualmente disponibili, rischierebbe di saturare la memoria RAM e allungare i tempi.

I generatori sono funzioni speciali, in cui i risultati ottenuti dallo scorrimento di una sequenza vengono restituiti on demand, cioè man mano che il programma ne richiede la disponibilità.

Ciò significa che il file viene letto in modo da assicurare la immediata disponibilità di un'informazione, che puo' essere trattata ignorando le informazioni successive.

Non essendo più necessari i file intermedi di appoggio, forniamo solo definizione del file SVG, e la forniamo alla classe attraverso il suo inizializzatore o costruttore.

Per agevolare la comprensione, spezzo il codice nelle sezioni corrispondenti ai metodi, che pubblico una alla volta per commentarle immediatamente dopo. Il lavoro non è ancora concluso, usero' ancora una volta (l'ultima) un file per riversare i dati sul disco fisso, ma sarà evidente il guadagno di tempo e memoria e la maggiore snellezza del codice.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# -*- coding: utf-8 -*-
#|/usr/bin/env python
import re
import xml.etree.ElementTree as ET

class SVG2Tk:
    """La classe è finalizzata ad assicurare la traduzione, per quanto possibile
    fedele alle specifiche SVG, negli oggetti caratteristici del widget Canvas
    di Tkinter"""

    def __init__(self, file_in):
        
        self.file_in = open(file_in, 'r')

La prima parte non è di difficile comprensione, riprende più o meno i precedenti contenuti. Ho eliminato i parametri riferiti ai nomi dei file di appoggio e ho inserito delle righe di commento che vanno a comporre la documentazione della classe così come si puo' ottenere con i normali strumenti di documentazione di Python: lo vedremo più chiaramente alla fine.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    def s_match(self, pattern, d):
        """restituisce una stringa contenente la substringa che soddisfa
            il pattern fornito alla funzione, e la stringa risultante dopo
            l'estrazione della substringa"""
        
        matchObj = re.match(pattern, d)
        if matchObj:
            string = matchObj.group()
            d = d.replace(string, "")
            s_string = re.split(r'[;\s]*',string)
            string = ','.join(s_string[:-1])
            return (string, d)
        return None

Anche questa sezione, che utilizza le regular expressions per il match tra stringhe di SVG e modelli, non differisce dalla precedente.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    def xmlrecur(self,x):
        "restituisce l'insieme degli attributi che soddisfano le stringhe di ricerca "
        for i in x:
            if set(['id','style','d']).issubset(i.attrib):
                yield (i.attrib['id'], i.attrib['style'], i.attrib['d'])
            elif set(['width','height']).issubset(i.attrib):
                yield ('width - height', i.attrib['width'], i.attrib['height'])
            else:
                for j in self.xmlrecur(i):
                    yield j

La routine xmlrecur è stata, invece, pesantemente rimaneggiata. Ogni volta che l'identificazione dell'attributo è positiva, uso la word yield che è simile al return, cioè alla restituzione dei risultati delle attività previste dal metodo, ma avviene ogni volta che ho necessità di fornire al codice chiamante dei valori senza interrompere la scansione dell'iterable, in questo caso del file di origine dei dati, che viene solo temporaneamente sospesa. Ad ogni successiva chiamata verrà ripresa la scansione e verranno forniti ulteriori risultati.

Ciò che rende di difficile comprensione il metodo è il fatto che apparentemente viene restituito al codice chiamante un dato, mentre in realtà viene restituito un metodo, che si chiama generatore.

Vediamo la sezione finale e ne discutiamo immediatamente.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    def re_load(self):

        tree = ET.parse(self.file_in)
        root = tree.getroot()
        generatore = self.xmlrecur(root)
        tk_out = open('tk_out','w')
        for i in generatore:
            if i[0] == 'width - height':
                self.width  = float(i[1])
                self.height = float(i[2])
            else:
                self.id    = i[0]
                self.style = i[1]
                self.d     = i[2]
                try:
                    pattern=[
                    r'([M]{1}[\s]*[-\d]*[\.]?[\d]*[\,\s]?[-\d]*[\.]?[\d]*[\s]*)',
                    r'([L]{1}[\s]*[-\d]*[\.]?[\d]*[\,\s]{1}[-\d]*[\.]?[\d]*[\s]*)',
                    r'([C]{1}[\s]*)(([-\d]*[\.]?[\d]*[\,\s]{1}[-\d]*[\.]?[\d]*[\s]*){3})',
                    r'([z]{1}[\s]*)'
                    ]
                    
                    while len(self.d) > 0:
                        for p in pattern:
                            ret = self.s_match(p, self.d)
                            if ret != None:
                                string = ret[0]
                                self.d = ret[1]
                                tk_out.write(string+'\n')                            
                except Exception:
                    print Exception
                    tk_out.close()
                    break

if __name__ == '__main__':
    app = SVG2Tk('EU-Italy.svg')
    app.re_load()

La funzione re_load è simile alla precedente, ma ne differisce per il fatto che i dati ottenuti dalla scansione vengono caricati, non come dati, ma come metodo, sull'item generatore da cui vengono estratti uno alla volta e sottoposti al matching. Non è intuitivo, il generatore non contiene alcun dato, non è una lista, bensì una chiamata iterativa al metodo recurxml che estrae i dati on demand. Ci ho messo parecchio a capire come funziona questo metodo e rinuncio a tentare di spiegarlo ulteriormente. Se avete voglia di discuterlo mandatemi un commento e ne parliamo insieme.

Nella sezione finale inizializzo la class col solo nome del file SVG e poi faccio una chiamata al metodo re_load che fa girare il tutto. Nel prossimo post eliminerò anche l'ultimo puntello, cioè il file di appoggio, e farò in modo che l'estrazione avvenga in streaming con la creazione degli oggetti di Tkinter.Canvas. Alla prossima

Saturday, March 1, 2014

Miglioramento del codice

Lavorando in Python si apprezza la facilità con cui si colgono le imperfezioni del codice, quasi subito dopo averlo scritto. A volte sono puramente estetiche e basta seguire le indicazioni della PEP 8 (Python Enhancement Proposal) per ottenere un risultato migliore. Altre volte bisogna cogliere ridondanze e mancanza di snellezza del codice. Ora che ho verificato che il codice del post precedente funziona, posso iniziare a migliorarlo.

  1. trasferisco le importazioni delle librerie esterne fuori della classe
  2. limito le funzioni del costruttore alla inizializzazione dei file esterni
  3. riduco la ridondanza, spostando su una funzione la ricerca del pattern sulla stringa 'd' e creando una lista di pattern da leggere iterativamente. la funzione s_match restituisce sia la stringa risultante dal match pattern/stringa d, sia la stringa d modificata, per mezzo di una tupla
  4. affido l'esecuzione del programma al metodo re_load che gestisce anche le exception per le espressioni regolari

E questo è il risultato, un po' più pitonico:


#|/usr/bin/env python
import re
import xml.etree.ElementTree as ET

class SVG2Tk:
    
    def __init__(self, file_s, file_out):

        self.file_s = open(file_s, 'r')
        self.file_out= open(file_out,'w')
        self.tk_out = open('tk_out.txt','w')


    def s_match(self, pattern, d):
        matchObj = re.match(pattern, d)
        if matchObj:
            string = matchObj.group()
            d = d.replace(string, "")
            s_string = re.split(r'[;\s]*',string)
            string = ','.join(s_string[:-1])
            return (string, d)
        return None
    
    def xmlrecur(self,x):
        for i in x:
            if set(['id','style','d']).issubset(i.attrib):
                    self.file_out.write(i.attrib['id'])
                    self.file_out.write('\n')
                    self.file_out.write(i.attrib['style'])
                    self.file_out.write('\n')
                    self.file_out.write(i.attrib['d'])
                    self.file_out.write('\n')
            elif set(['width','height']).issubset(i.attrib):
                self.file_out.write(i.attrib['width'])
                self.file_out.write('\n')
                self.file_out.write(i.attrib['height'])
                self.file_out.write('\n')
                
            else:
                self.xmlrecur(i)

    def re_load(self, file_out):

        tree = ET.parse(self.file_s)
        root = tree.getroot()
        self.xmlrecur(root)
        self.file_out.close()
        
        self.file_in = open(file_out, 'r')
        
        try:
            width=self.file_in.next()
            height=self.file_in.next()
            self.tk_out.write('width %sheight %s' % (width, height))
        except Exception:
            print Exception

        while True:
            
            try:
                id_ = self.file_in.next()
                style = self.file_in.next()
                d = self.file_in.next()

                self.tk_out.write('id,' + id_)
                pattern=[
                r'([M]{1}[\s]*[-\d]*[\.]?[\d]*[\,\s]?[-\d]*[\.]?[\d]*[\s]*)',
                r'([L]{1}[\s]*[-\d]*[\.]?[\d]*[\,\s]{1}[-\d]*[\.]?[\d]*[\s]*)',
                r'([C]{1}[\s]*)(([-\d]*[\.]?[\d]*[\,\s]{1}[-\d]*[\.]?[\d]*[\s]*){3})',
                r'([z]{1}[\s]*)'
                ]
                
                while len(d) > 0:
                    for p in pattern:
                        ret = self.s_match(p, d)
                        if ret != None:
                            string = ret[0]
                            d = ret[1]
                            self.tk_out.write(string+'\n')
                        
            except Exception:
                print Exception
                self.tk_out.close()
                break



if __name__ == '__main__':
    app = SVG2Tk('EU-Italy.svg','out_xml.txt')
    app.re_load('out_xml.txt')

Modificata così la classe, procederemo all'ultimo step, cioè alla traduzione in Tkinter.

How to create a virtual linux machine with qemu under Debian or Ubuntu with near native graphics performance

It's been a long time since my latest post. I know, I'm lazy. But every now and then I like to publish something that other people c...