Tuesday, February 25, 2014

Dal file SVG all'istruzione create_polygon in Tkinter. Uso delle espressioni regolari

Non c'è strumento più importante, per la manipolazione di testi, delle espressioni regolari. Costituiscono uno strumento scomodo, per la loro scarsa leggibilità, ma nello stesso tempo potente perchè consentono di leggere stringhe di testo in cui la disposizione e il numero di caratteri puo' essere estremamente variabile, ma presenta delle regolarità di modello, o pattern.

Prendiamo, per esempio, una stringa in cui compaiono il cognome e il nome di un certo numero di persone, una per stringa. Supponiamo che il software che ha generato le stringhe abbia assegnato caratteri tutti maiuscoli al cognome e solo l'iniziale maiuscola al nome.

Il pattern, scritto in forma verbosa, sarà pertanto il seguente:

Leggi da inizio riga una o più stringhe formate da soli caratteri maiuscoli, eventualmente intervallati e seguiti da spazi.

Leggi quindi una o più stringhe formate da 1 carattere alfabetico maiuscolo seguito da un certo numero di caratteri minuscoli e spazi.

Il pattern così descritto potrà essere tradotto in un'espressione regolare, che potremo cimentare contro ognuna delle stringhe formanti il cognome e nome delle persone in elenco.

In Python si usano pattern identici a quelli usati in linguaggio Perl e simili a quelli che troviamo in quasi tutti i linguaggi di programmazione, da C++ a Java, perfino a VBA per Excel.

Il pattern sopra descritto puo' essere reso dalla seguente espressione regolare:

([A-Z]+[\s]+)+([A-Z]{1}[a-z]+[\s]?)+

che, ovviamente, è pressochè illeggibile.

Non c'è alternativa allo studio analitico dei simboli utilizzati nelle espressioni regolari. Un buon tester/editor per collaudare le vostre regexp è il seguente: pythex

Con il suo aiuto, e con molta pazienza, potete voi stessi comporre espressioni regolari anche molto complesse e sperimentarle su stringhe test.


Veniamo alla traduzione SVG->Tkinter. Qualche post fa abbiamo estratto dai file SVG un intero set di stringhe di coordinate associate al tag <path> come attributi. Ora si tratta di ripescarle dai file (più avanti elimineremo il passaggio intermedio, costruendo un parser) e convertirle in istruzioni datate di senso nell'equivalente Tkinter di che è create_polygon.

Come ricorderete, ho salvato le stringhe in gruppi di tre, 'id','style' e 'd': la prima stringa è l'attribuzione identificativa fatta dal software grafico per riconoscere, nel blocco path, a cosa si riferisce il blocco stesso. Poi c'è la stringa style, che rappresenta il complesso delle informazioni di formato che permettono di personalizzare la poligonale. Infine la stringa d contiene le coordinate di percorso. Non abbiamo incertezze sul fatto che i tre gruppi di informazioni esistano tutti, per cui possiamo procedere.

Una piccola modifica alle stringhe di ricerca mi permette di catturare un'informazione utile per il futuro dimensionamento dell'immagine. Cerco di intercettare gli attrib che identificano la larghezza (width) e l'altezza (height) dell'immagine. Trattandosi di informazioni generali, verranno intercettate molto presto dalla procedura ricorsiva e inserite nel file in uscita per prime.

Il programma seguente, molto preliminare e non necessariamente funzionante in tutti i contesti e con tutti i file SVG, ricerca ricorsivamente gli attributi corrispondenti alle variabili di interesse e le salva in un file in modo da poter essere rilette con molta tranquillità in una fase successiva. Il salvataggio dei dati in un file non è un passaggio obbligatorio, ma ci aiuta a costruire il programma intercettando gli errori.

Per le prove ho utilizzato il file SVG: EU-Italy.svg.

Di seguito invece il programma che sto elaborando per la manipolazione di questo file.


 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#|/usr/bin/env python
import re

class SVG2Tk:

    import xml.etree.ElementTree as ET
    
    def __init__(self, file_s, file_out):

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

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

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

                pattern1 = r'([M|m]{1}[\s]*)([\d]*[\.]?[\d]*[\,]?[\d]*[\.]?[\d]*)'
                matchObj = re.search(pattern1, d)
                if matchObj:
                    print matchObj.groups()
                pattern2 = r'([L|l]{1}[\s]*)([\d]*[\.]?[\d]*[\,]{1}[\d]*[\.]?[\d]*)'
                for matchOb2 in re.finditer(pattern2, d):
                        print matchOb2.groups()
                pattern3 = r'([C|c]{1}[\s]*)(([\d]*[\.]?[\d]*[\,]{1}[\d]*[\.]?[\d]*[\s]+){3})'
                matchOb3 = re.search(pattern3, d)
                if matchOb3:
                    print matchOb3.group(1,2)
                    
            except Exception:
                print 'exception'
                break
                

    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)

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

Il codice presenta alcune modalità di utilizzo delle espressioni regolari in Python:

  • La libreria di riferimento è re, che viene importata in testa al programma
  • con la opzione re.search vengono cimentati dei pattern con le stringhe che, con metodo iterativo, sono estratte dal file salvato.
  • La variabile matchObj e analoghe (nomi di fantasia, come sempre) sono oggetti delle libreria re, non sono utilizzabili in modo diretto. I metodi group() e groups() servono per estrarre le sottostringhe corrispondenti ai pattern. Al momento viene fatta esclusivamente una stampa su console dei risultati, come si vede dalla stampa seguente.
Python 2.7.5+ (default, Sep 19 2013, 13:48:49) 
[GCC 4.8.1] on linux2
Type "copyright", "credits" or "license()" for more information.
==== No Subprocess ====
>>> 
width 207036
height 173880

('M ', '168720,114651')
('C ', '170405.84,114660.61 171877.76,115218.21 173517.99,115465.97\n')
('M ', '790.46875,0.875')
('L ', '426.9375,0.90625')
('L ', '426.46875,1.5')
('L ', '423.84375,2.25')
('L ', '418.375,7')
('L ', '416.65625,8.28125')
('L ', '414.53125,8.625')
('L ', '413,6.90625')
('L ', '411.75,4.5')
('L ', '410.96875,2.15625')
('L ', '409.15625,1.5625')
('L ', '408.1875,1.96875')
('L ', '406.875,3.96875')
('L ', '404.25,4.96875')
('L ', '402.25,7.15625')
('L ', '401.375,9.875')
('L ', '400.09375,11.1875')
('L ', '398.46875,11.3125')
('L ', '397.09375,10.53125')
('L ', '395.6875,10.90625')
('L ', '395.34375,12.28125')
('L ', '396,13.25')
('L ', '396.125,14.875')
('L ', '395.125,15.75')
('L ', '390,15.4375')
('L ', '388.65625,14.65625')
('L ', '387.03125,14.3125')
('L ', '385.09375,15.5625')
('L ', '383.5,18.71875')
('L ', '380.96875,21.5625')
('L ', '380.75,25.03125')
('L ', '384.3125,31.5')
('L ', '386.3125,33.03125')
('L ', '389.3125,33.65625')
('L ', '391.4375,33.34375')
('L ', '396.53125,30.65625')
('L ', '400.21875,31.34375')

In un prossimo post faro' un'ulteriore elaborazione del codice, eliminando il file di appoggio e iniziando la traduzione in codice Tkinter. Come penso possiate apprezzare, la combinazione di strumenti raffinati come le librerie di Python consente, con pochissimo codice, di affrontare problemi di notevole complessità. Sono interessato a qualche vostro commento, anche per migliorare il codice e pulirlo un po' dalla grezzezza del mio stile. A presto.

No comments:

Post a Comment

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...