Unicode Error beim parsen eines XML Dokuments

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
brauns
User
Beiträge: 11
Registriert: Mittwoch 24. Oktober 2007, 14:26

Hallo zusammen,
ich habe eine Frage zu einem Python Problem.
Ich möchte eine XML Datei mit einem Skript parsen.

Leider erhalte ich folgenden Fehler:
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 1: ordinal not in range(128)
Ich habe heute schon den ganzen Tag gesucht - jedoch bin ich nicht fündig geworden.

Eine Testdatei zum Parsen findet man hier:
http://132.230.29.38/openlayer/convert/ ... 102007.osm

Hier noch der Code - vielleicht hat jemand einen Tipp für mich ?

Vielen Dank
brauns

Code: Alles auswählen

from xml.sax import make_parser, handler
from math import sqrt
from xml.sax.saxutils import escape
import osr

class OSMNode:
    def __init__( self, id=None, geom=None, tags=None):
        self.id = id
            
        if tags is None:
            self.tags = {}
        else:
            self.tags = tags
        
    def __str__( self ):
        return "%s, %s, %s" % (str(self.id), str(self.geom), str(self.tags))
        
class OSMWay:
    def __init__( self, id=None, nodes=None, tags=None ):
        self.id = id
        
        if nodes is None:
            self.nodes = []
        else:
            self.nodes = nodes
            
        if tags is None:
            self.tags = {}
        else:
            self.tags = tags
        
    def __str__( self ):
        return "%d, %s, %s" % ( self.id, str( self.nodes ), str(self.tags) )

class OSMFile:

    class OSMHandler(handler.ContentHandler):

        def __init__(self):
            self._parent = None
            self._curr_node = None
            self._curr_way = None
            self.nodes = {}
            self.ways = {}

        def startElement( self, name, attrs ):
            if name == 'node':
                self._parent = 'node'
                self._curr_node = OSMNode()
                self._curr_node.id = int( attrs['id'] )
                self._curr_node.geom = ( float(attrs['lon']), float(attrs['lat']) )
                
            if name == 'way':
                self._parent = 'way'
                self._curr_way = OSMWay()
                self._curr_way.id = int( attrs['id'] )
                
            if name == 'tag':
                if self._parent == 'node':
                    self._curr_node.tags[ str(attrs['k']) ] = str(attrs['v'])
                elif self._parent == 'way':
                    self._curr_way.tags[ str(attrs['k']) ] = str(attrs['v'])
                    
            if name == 'nd':
                self._curr_way.nodes.append( int(attrs['ref']) )
                    
        def endElement( self, name ):
            if name == 'node':
                self.nodes[ self._curr_node.id ] = self._curr_node
                self._curr_node == None
            
            if name == 'way':
                self.ways[ self._curr_way.id ] = self._curr_way
                self._curr_way == None

    def __init__( self, filename=None ):
        # Set up member collections
        self.nodes = {} #hash of id->(geom,tags)
        self.ways = {}  #hash of id->(nodes, tags)
        
        if filename is not None:
           self.parse_osmfile( filename )
	   
        
    def parse_osmfile( self, filename ):
        osm_handler = OSMFile.OSMHandler()
                    
        parser = make_parser()
        parser.setContentHandler(osm_handler)
        parser.parse(filename)

        self.nodes = osm_handler.nodes
        self.ways  = osm_handler.ways
            
    def connected_nodes( self ):
        """Returns a list of all nodes that are referenced by more than one way"""
        
        once = {}
        twice = []
        
        for id, parsedway in self.ways.iteritems():
            for node in parsedway.nodes:
                if node in once:
                    twice.append( node )
                else:
                    once[node] = True
                    
        return twice
        
    def split_way( self, way, split_nodes, top=True ):
        
        if top:
            id = way.id*1000 # open up a space for split id, but only if this is the first recusion level
        else:
            id = way.id
        
        # Iterate down to the split point
        i=1
        for node in way.nodes[1:-1]:     #don't want to split at the endpoints
            if node in split_nodes:
                #Split the Way at the splitpoint
                left  = OSMWay(id, way.nodes[:i+1], way.tags)
                right = OSMWay(id+1, way.nodes[i:], way.tags)
                
                # Recurse!
                return [ left ] + self.split_way(right, split_nodes, top=False)
            i = i+1
            
        # Way was never split
        return [ OSMWay(id, way.nodes, way.tags) ]
        
    def split_ways( self ):
        """Returns osmfile object with split-up ways"""
        
        split_ways = {}
        
        split_nodes = self.connected_nodes()
        
        for id, way in self.ways.iteritems():
            xs = self.split_way( way, split_nodes )
            for way in xs:
                split_ways[way.id] = way
        
        ret = OSMFile()
        ret.nodes = self.nodes
        ret.ways = split_ways
        
        return ret

    from_proj = osr.SpatialReference()
    from_proj.SetWellKnownGeogCS( "EPSG:4326" )
    
    to_proj = osr.SpatialReference()
    to_proj.ImportFromProj4('+proj=merc')

    tr = osr.CoordinateTransformation( from_proj, to_proj )

    def project( self, point ):
        pt = self.tr.TransformPoint( point[0], point[1] )
        return (pt[1], pt[0])
    
    def way_length( self, way_id ):
        way = self.ways[way_id]
        
        geom = [self.project( self.nodes[id][0] ) for id in way.nodes]
            
        accum = 0
        for i in range(0, len(geom)-1):
            x1, y1 = geom[i]
            x2, y2 = geom[i+1]
            
            accum += sqrt( (x2-x1)**2 + (y2-y1)**2 )
            
        return accum
        
    def to_osm( self ):
        ret = []
        ret.append( "<?xml version='1.0' encoding='UTF-8'?>" )
        ret.append( "<osm version='0.5' generator='libosm'>" )
        
        for id, node in self.nodes.iteritems():
            ret.append("  <node id='%d' visible='true' lat='%f' lon='%f'>"%(id, node.geom[1], node.geom[0]))
            for k, v in node.tags.iteritems():
                ret.append( "    <tag k=\"%s\" v=\"%s\" />"%(k,escape(v)) )
            ret.append("  </node>")
                
        for id, way in self.ways.iteritems():
            ret.append( "  <way id='%d' visible='true'>"%id )
            for node in way.nodes:
                ret.append( "    <nd ref='%d' />"%node )
            for k, v in way.tags.iteritems():
                ret.append( "    <tag k=\"%s\" v=\"%s\" />"%(k,escape(v)) )
            ret.append("  </way>")
                
        ret.append( "</osm>" )
        
        return "\n".join(ret)
        
if __name__ == '__main__':
    
    print "Reading OSM file..."
    osmfile = OSMFile( 'Freiburg_17102007.osm' )
    print "Done"
    
    print "%d nodes, %d ways" % (len(osmfile.nodes), len(osmfile.ways))
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Lies Dir am besten erstmal das hier durch: http://www.python-forum.de/viewtopic.php?p=56366
MfG
HWK
brauns
User
Beiträge: 11
Registriert: Mittwoch 24. Oktober 2007, 14:26

Danke, dieser Link ist sehr interessant.

Der Fehler der bei mir beim Ausführen des PythonScripts kommt verstehe ich jedoch nicht wirklich.
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 1: ordinal not in range(128)
\xf6 meint ja das da ein ö nicht encodiert werden kann oder ?

Mein Linux Terminal ist UTF-8. Das Python Script ASCII.

XML File speichere ich auch als UTF-8 ab.

Habe ich hier vielleicht schon einen grundlegenden Fehler in den Konventionen ?
BlackJack

Wo taucht denn der Fehler auf? Bitte komplette Tracebacks posten.
brauns
User
Beiträge: 11
Registriert: Mittwoch 24. Oktober 2007, 14:26

Beim Aufruf von "python libosm.py" erscheint folgender Fehler:

Reading OSM file...
Traceback (most recent call last):
File "libosm.py", line 203, in <module>
osmfile = OSMFile( 'Freiburg_17102007.osm' )
File "libosm.py", line 83, in __init__
self.parse_osmfile( filename )
File "libosm.py", line 91, in parse_osmfile
parser.parse(filename)
File "/usr/lib/python2.5/site-packages/_xmlplus/sax/expatreader.py", line 109, in parse
xmlreader.IncrementalParser.parse(self, source)
File "/usr/lib/python2.5/site-packages/_xmlplus/sax/xmlreader.py", line 123, in parse
self.feed(buffer)
File "/usr/lib/python2.5/site-packages/_xmlplus/sax/expatreader.py", line 216, in feed
self._parser.Parse(data, isFinal)
File "/usr/lib/python2.5/site-packages/_xmlplus/sax/expatreader.py", line 312, in start_element
self._cont_handler.startElement(name, AttributesImpl(attrs))
File "libosm.py", line 61, in startElement
self._curr_node.tags[ str(attrs['k']) ] = str(attrs['v'])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 1: ordinal not in range(128)



Vielen Dank
brauns
BlackJack

Da wird versucht aus Unicode-Zeichenketten eine Bytekette zu machen. Das geht halt nicht wenn etwas ausserhalb von ASCII in der Unicode-Zeichenkette vorkommt, weil ja nicht klar ist wie es kodiert werden soll. Du musst da entweder `encode()` mit einer expliziten Kodierung verwenden oder es bei Unicode belassen.
brauns
User
Beiträge: 11
Registriert: Mittwoch 24. Oktober 2007, 14:26

Vielen Dank erstmal.

Wie kann ich dies denn Umsetzen ?

1. Wie kann man das bei Unicode belassen.


2. Umkonvertieren, meinst du so ?
self._curr_node.tags[ str(attrs['k']) ] = str(attrs['v'].encode("iso-8859-1"))

Vielen Dank
brauns
brauns
User
Beiträge: 11
Registriert: Mittwoch 24. Oktober 2007, 14:26

Ich habe in der XML Datei folgendes Zeichen von Hand ersetzt:
ö -> oe
ä -> ae
ü -> ue
é -> e
ß -> ss

Jetzt läuft das Skript ohne Fehlermeldung durch.
Es liegt also wirklich nur an den Umlauten und Sonderzeichen.

Leider weiß ich nicht wirklich wo ich da ansetzen muss.

Das Python Script in UTF-8 abgespeichert ist okay oder ?
Das XML habe ich auch in UTF-8 abgespeichert.

Wieso liest Python dann eigentlich ASCII und nicht UTF-8 ?
BlackJack

Wie das Python-Skript kodiert ist hat damit nichts zu tun. Python liesst hier kein ASCII, Du parst ein XML-Dokument und bekommst Unicode-Zeichenketten. XML enthält Unicode, das ist so im Standard definiert. Und macht ja auch Sinn wenn man mit Texten arbeitet.

Dein Problem tritt auf, wenn Du aus den Zeichen Bytes machen möchtest ohne die Kodierung anzugeben. Warum willst Du das an dieser Stelle machen? Du solltest Dich dringend mit Unicode auseinandersetzen, insbesondere wenn Du mit XML arbeiten willst.
brauns
User
Beiträge: 11
Registriert: Mittwoch 24. Oktober 2007, 14:26

Okay, das klingt natürlich logisch.

Aber an welcher Stelle werden denn aus Zeichen Bytes gemacht ?
BlackJack

Bei `str()` machst Du aus einer Unicode-Zeichenkette, die zum Beispiel den Buchstaben 'ä' enthalten kann, eine Folge von Bytes. Zu welchen Bytewerten die Zeichen im ASCII-Bereich werden sollten ist relativ eindeutig, das ist bei den meisten Kodierungen gleich, aber ein 'ä' kann man auf verschiedene Arten in ein oder mehrere Bytes umwandeln. Da muss man also angeben wie ein 'ä' kodiert werden soll.
brauns
User
Beiträge: 11
Registriert: Mittwoch 24. Oktober 2007, 14:26

Okay, vielen vielen Dank.
Jetzt hab ich es hinbekommen.
Folgende Änderung ist dann nötig

Code: Alles auswählen

  if name == 'tag':
                if self._parent == 'node':
                    self._curr_node.tags[ str(attrs['k']) ] = str(attrs['v'].encode("utf-8"))
                elif self._parent == 'way':
                    self._curr_way.tags[ str(attrs['k']) ] = str(attrs['v'].encode("utf-8"))
Super Forum habt ihr hier !
Danke :-)
Antworten