UnicodeDecodeError

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
HeiFa
User
Beiträge: 1
Registriert: Donnerstag 27. November 2014, 14:40

Hallo zusammen.
Ich bin neu hier im Forum und auch was das Programmieren angeht ein Neuling.

GANZ unten ist nur der Code eingefügt.

Leider weiß ich nicht, welcher Codeabschnitt relevant wäre, daher habe ich wahrscheinlich zuviel eingefügt, ich bitte um Nachsicht.
Wenn jemand meint zu wissen, was ich rausnehmen kann um die Übersichtlichkeit zu wahren oder welchen Tag ich Editieren muss im Forum um den Text zu Beginn versteckt zu haben, bitte mitteilen. Vielen Dank.

Zur Zeit beschäftige ich mich mit einem kleinen Tool, welches von dem User snorfalorpagus auf [url =https://github.com/snorfalorpagus/avl2qml]GitHub[/url] zur Verfügung gestellt wurde.
Diese Tool soll .avl-Dateien einlesen, passend formatieren und dann als .qml-Dateien neu schreiben.
Diese Dateien sind simple Textdateien.
Dieses Datenformat ist aus dem GIS-Bereich Wikipedia.

Diese Tool nutze ich auf Linux Mint 17 per Konsole.

Ein kleines Problem im GitHub-Code ist zur Zeit noch, dass ein bestimmtes Format und bestimmte Daten einfach vorausgesetzt werden, bei deren Fehlen das Programm kracht. Diesen Fehler habe ich soweit ausbügeln können.

Ich möchte gerne den Code so anpassen, dass die .avl-Dateien auch mit Umlauten bearbeitet werden.
Wie mache ich das denn? Von Python habe ich bislang keine Ahnung, werde damit aber künftig mehr zu tun haben.

Meine Frage:
Woran liegen die Fehlermeldungen.
Was muss ich ändern, damit deutsche Umlaute mit abgefangen werden.

PROBLEM:

Wenn in den avl.-Dateien deutsche Umlaute vorhanden sind, gibt es eine Fehlermeldung.
Traceback (most recent call last):
File "avl2qml.py", line 308, in <module>
qml = avl2qml(data, shapefile=args.shp[0], field_name=args.field[0])
File "avl2qml.py", line 271, in avl2qml
qml = '''<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>\n''' + ET.tostring(qgis).decode('utf-8')
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1126, in tostring
ElementTree(element).write(file, encoding, method=method)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 820, in write
serialize(write, self._root, encoding, qnames, namespaces)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 939, in _serialize_xml
_serialize_xml(write, e, encoding, qnames, None)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 932, in _serialize_xml
v = _escape_attrib(v, encoding)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1090, in _escape_attrib
return text.encode(encoding, "xmlcharrefreplace")
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 10: ordinal not in range(128)
Wenn ich händisch alle Umlaute aus den .avl-Datein nehme und das Programm drüber läuft, funktioniert alles einwandfrei.
Nun kann ich aber nicht jede Datei einzeln manuell durchgehen und vor und nach dem Bearbeiten die Umlaute ändern.

Wenn ich in jede einzelne Datei, die Umlaute enthält die magicline

Code: Alles auswählen

# -*- coding: utf-8 -*-
eingebe, gibt es einen neuen Fehler:
Traceback (most recent call last):
File "avl2qml.py", line 308, in <module>
qml = avl2qml(data, shapefile=args.shp[0], field_name=args.field[0])
File "avl2qml.py", line 18, in avl2qml
odb = pyodb.ODB(data)
File "/home/heidkamp/Dokumente/Projekte/APW_001/avl2qml-master/pyodb.py", line 32, in __init__
buf[-1] += line + '\n'
IndexError: list index out of range
Hier der Code vom Hauptprogramm.

Code: Alles auswählen

#!/usr/bin/env python

'''
avl2qml - module for converting ArcView 3.x Legends (.avl) to QGIS styles (.qml)
'''

import argparse
import xml.etree.ElementTree as ET
import os
import re

from pprint import pprint

import pyodb

def avl2qml(data, shapefile=None, field_name=None):
    # parse avl
    odb = pyodb.ODB(data)
    legend = odb.objects[1].attrs['Roots'] # assumes legend is the first root
    if isinstance(legend, list):
        legend = odb.objects[legend[0]]
    else:
        legend = odb.objects[legend]

    # create qml style document
    qgis = ET.fromstring('<qgis version="2.0.1-Dufour" minimumScale="-4.65661e-10" maximumScale="1e+08" minLabelScale="0" maxLabelScale="1e+08" hasScaleBasedVisibilityFlag="0" scaleBasedLabelVisibilityFlag="0" />')
    renderer = ET.SubElement(qgis, 'renderer-v2', {'symbollevels': '0'})

    if field_name is None:
        # include field name, if it's specified
        if hasattr(legend, 'field_names'):
            field_name = legend.field_names.attrs['S']
            if shapefile is not None:
                # attempt to correct case of field name
                import ogr
                ds = ogr.Open(shapefile)
                layer = ds.GetLayer()
                defn = layer.GetLayerDefn()
                for n in range(0, defn.GetFieldCount()):
                    name = defn.GetFieldDefn(n).GetName()
                    if field_name.lower() == name.lower():
                        field_name = name
            renderer.attrib['attr'] = field_name
    else:
        renderer.attrib['attr'] = field_name

    if legend.attrs['LegType'] == '0x01':
        # single symbol
        renderer.attrib['type'] = 'singleSymbol'
    if legend.attrs['LegType'] == '0x02':
        # graduated symbols
        ranges = ET.SubElement(renderer, 'ranges')
        renderer.attrib['type'] = 'graduatedSymbol'
    elif legend.attrs['LegType'] == '0x08':
        # categorized symbols
        categories = ET.SubElement(renderer, 'categories')
        renderer.attrib['type'] = 'categorizedSymbol'

    symbols = ET.SubElement(renderer, 'symbols')

    n = 0
    for lclass in legend.classes:
        if hasattr(lclass, 'symbol'):

            # define class

            if legend.attrs['LegType'] == '0x01':

                pass # nothing to do here

            elif legend.attrs['LegType'] == '0x02':

                rng = ET.SubElement(ranges, 'range')
                rng.attrib['symbol'] = str(n)
                # HACK: QGIS doesn't match '1.0' to '1'
                if 'MinNum' in lclass.attrs:
                    if lclass.attrs['MinNum'] == int(lclass.attrs['MinNum']):
                        rng.attrib['lower'] = str(int(lclass.attrs['MinNum']))
                    else:
                        rng.attrib['lower'] = str(lclass.attrs['MinNum'])
                if 'MaxNum' in lclass.attrs:
                    if lclass.attrs['MaxNum'] == int(lclass.attrs['MaxNum']):
                        rng.attrib['upper'] = str(int(lclass.attrs['MaxNum']))
                    else:
                        rng.attrib['upper'] = str(lclass.attrs['MaxNum'])
                if lclass.label is not None:
                    rng.attrib['label'] = lclass.label
                else:
                    rng.attrib['label'] = ''

            elif legend.attrs['LegType'] == '0x08':

                category = ET.SubElement(categories, 'category')
                category.attrib['symbol'] = str(n)
                if 'MinNum' in lclass.attrs:
                    if lclass.attrs['MinNum'] == int(lclass.attrs['MinNum']):
                        # HACK: QGIS doesn't match '1.0' to '1'
                        category.attrib['value'] = str(int(lclass.attrs['MinNum']))
                    else:
                        category.attrib['value'] = str(lclass.attrs['MinNum'])
                elif 'MinStr' in lclass.attrs:
                    category.attrib['value'] = lclass.attrs['MinStr']
                else:
                    category.attrib['value'] = str(lclass.attrs['Label'])
                if lclass.label is not None:
                    category.attrib['label'] = lclass.label
                else:
                    category.attrib['label'] = ''

            if type(lclass.symbol) is pyodb.ODBObject:
                raise NotImplementedError('Symbol {} has not yet been implemented.'.format(lclass.symbol.object_type))

            # define symbol for class

            symbol = ET.SubElement(symbols, 'symbol')
            symbol.attrib['name'] = str(n)
            symbol.attrib['alpha'] = '1'

            if legend.sym_type == '0x01':

                # symbol type is line
                symbol.attrib['type'] = 'line'

                if hasattr(lclass.symbol, 'color') or 1:
                    layer1 = ET.SubElement(symbol, 'layer')
                    layer1.attrib['pass'] = '0'
                    layer1.attrib['class'] = 'SimpleLine'
                    layer1.attrib['locked'] = '0'
                    properties = {
                        'capstyle': 'square',
                        'color': ','.join([str(x) for x in lclass.symbol.color.rgba_8bit]),
                        'customdash': '5;2',
                        'customdash_unit': 'MM',
                        'joinstyle': 'bevel',
                        'offset': '0',
                        'offset_unit': 'MM',
                        'penstyle': 'solid',
                        'use_custom_dash': '0',
                        'width_unit': 'MM',
                    }
                    if hasattr(lclass.symbol, 'width'):
                        properties['width'] = str(lclass.symbol.width * 0.26)
                    else:
                        properties['width'] = '0.26'
                    for k,v in list(properties.items()):
                        prop = ET.SubElement(layer1, 'prop')
                        prop.attrib['k'] = k
                        prop.attrib['v'] = v

            elif legend.sym_type == '0x02':

                # symbol type is fill
                symbol.attrib['type'] = 'fill'

                layer1 = ET.SubElement(symbol, 'layer')
                layer1.attrib['pass'] = '0'
                layer1.attrib['class'] = 'SimpleFill'
                layer1.attrib['locked'] = '0'
                properties = {
                    'border_width_unit': 'MM',
                    'color_border': ','.join([str(x) for x in lclass.symbol.outlinecolor.rgba_8bit]),
                    'offset': '0,0',
                    'offset_unit': 'MM',
                    'style': 'solid',
                    'style_border': 'solid',
                    'width_border': str(lclass.symbol.outlinewidth*0.26),
                }
                if 'Stipple' not in lclass.symbol.attrs:
                    properties['color'] = ','.join([str(x) for x in lclass.symbol.color.rgba_8bit])
                else:
                    properties['color'] = ','.join([str(x) for x in lclass.symbol.bgcolor.rgba_8bit])
                for k,v in list(properties.items()):
                    prop = ET.SubElement(layer1, 'prop')
                    prop.attrib['k'] = k
                    prop.attrib['v'] = v

                if 'Stipple' in lclass.symbol.attrs:
                    # don't know how to render 'Stipple' correctly - just use hatching instead
                    layer2 = ET.SubElement(symbol, 'layer')
                    layer2.attrib['pass'] = '0'
                    layer2.attrib['class'] = 'LinePatternFill'
                    layer2.attrib['locked'] = '0'
                    properties = {
                        'color': ','.join([str(x) for x in lclass.symbol.color.rgba_8bit]),
                        'distance': '2',
                        'distance_unit': 'MM',
                        'line_width_unit': 'MM',
                        'lineangle': '45',
                        'linewidth': '0.26',
                        'offset': '0',
                        'offset_unit': 'MM',
                    }
                    for k,v in list(properties.items()):
                        prop = ET.SubElement(layer2, 'prop')
                        prop.attrib['k'] = k
                        prop.attrib['v'] = v

                    subsymbol = ET.SubElement(layer2, 'symbol')
                    subsymbol.attrib['alpha'] = '1'
                    subsymbol.attrib['type'] = 'line'

                    layer3 = ET.SubElement(subsymbol, 'layer')
                    layer3.attrib['pass'] = '0'
                    layer3.attrib['class'] = 'SimpleLine'
                    layer3.attrib['locked'] = '0'
                    properties = {
                        'capstyle': 'square',
                        'color': ','.join([str(x) for x in lclass.symbol.outlinecolor.rgba_8bit]),
                        'customdash': '5;2',
                        'customdash_unit': 'MM',
                        'joinstyle': 'bevel',
                        'offset': '0',
                        'offset_unit': 'MM',
                        'penstyle': 'solid',
                        'use_custom_dash': '0',
                        'width': str(lclass.symbol.outlinewidth*0.26),
                        'width_unit': 'MM',
                    }
                    for k,v in list(properties.items()):
                        prop = ET.SubElement(layer3, 'prop')
                        prop.attrib['k'] = k
                        prop.attrib['v'] = v

            else:

                # symbol type is (probably) marker
                symbol.attrib['type'] = 'marker'

                layer1 = ET.SubElement(symbol, 'layer')
                layer1.attrib['pass'] = '0'
                layer1.attrib['class'] = 'SimpleMarker'
                layer1.attrib['locked'] = '0'

                properties = {
                    'horizontal_anchor_point': '1',
                    'name': 'circle',
                    'offset': '0,0',
                    'offset_unit': 'MM',
                    'outline_style': 'solid',
                    'outline_width': '0',
                    'outline_width_unit': 'MM',
                    'scale_method': 'area',
                    'size_unit': 'MM',
                    'vertical_anchor_point': '1'
                }

                if 'Size' in lclass.symbol.attrs:
                    properties['size'] = '{:.2f}'.format(float(lclass.symbol.attrs['Size']) / 5.0)
                else:
                    properties['size'] = '2.0' # default size

                # FIXME: are these the wrong way around?
                properties['color'] = ','.join([str(x) for x in lclass.symbol.color.rgba_8bit])
                properties['color_border'] = ','.join([str(x) for x in lclass.symbol.bgcolor.rgba_8bit])

                if 'Angle' in lclass.symbol.attrs:
                    properties['angle'] = '{:.0f}'.format(lclass.symbol.attrs['Angle'])
                else:
                    properties['angle'] = '0'

                for k,v in list(properties.items()):
                    prop = ET.SubElement(layer1, 'prop')
                    prop.attrib['k'] = k
                    prop.attrib['v'] = v

            n += 1

    indent(qgis)

    pprint(qgis)
    qml = '''<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>\n''' + ET.tostring(qgis).decode('utf-8')
    
    return qml

def indent(elem, level=0):
    '''
    Pretty formatting for xml.etree.ElementTree instances
    Source (public domain): http://effbot.org/zone/element-lib.htm#prettyprint
    '''
    i = "\n" + level*"  "
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
        for elem in elem:
            indent(elem, level+1)
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Convert an ArcView 3.x Legend (AVL) to a QGIS legend (QML)')
    parser.add_argument('avl', nargs=1, help='Path to *.avl')
    parser.add_argument('-p', dest='stdout', action='store_const', const=True, default=False, help='Print to STDOUT instead of writing to file')
    parser.add_argument('-f', '--field', dest='field', nargs=1, required=False, default=[None], help='Manually override field name')
    parser.add_argument('--shp', nargs=1, required=False, default=[None], help='Path to *.shp, used for correcting field name case')
    args = parser.parse_args()

    # read avl file
    f = open(args.avl[0], 'r')
    data = f.read()
    f.close()

    # convert the avl to qml
    qml = avl2qml(data, shapefile=args.shp[0], field_name=args.field[0])

    # write output
    if args.stdout is False:
        filename = re.sub('\.avl', '.qml', args.avl[0], re.IGNORECASE)
        f = open(filename, 'wb')
        f.write(qml.encode('utf-8'))
        f.close()
    else:
        print(qml)
BlackJack

@HeiFa: Die ``# -*- coding: utf-8 -*-``-Zeile ist nur für Python-Quelltexte, die in irgendwelche anderen Textbasierten Datendateien einzufügen macht keinen Sinn, sondern in vielen Fällen einfach nur die Daten ”kaputt” wenn das Datenformat nicht zufällig auch Kommentarzeilen besitzen darf die mit '#' anfangen.

Wichtig ist der Unterschied zwischen Bytestrings und Unicodestrings. Irgendwo fügt dieser Code dem XML-Objektbaum Bytestrings hinzu die nicht auf ASCII-Werte beschränkt sind, wenn ich das richtig sehe. Und darüber stolpert die Serialisierung in eine Zeichenkette dann am Ende.

Wenn man mit Texten arbeitet sollte man die so früh wie möglich nach Unicode dekodieren, also zum Beispiel gleich beim Einlesen. Dann ist die Frage ob `pyodb.ODB` mit Unicode klar kommt. Das wäre wahrscheinlich die einfachste Lösung. Denn ansonsten müsste man bei allem was aus diesem Objekt resultiert und dem XML hinzugefügt werden soll einzeln dekodieren.
Antworten