Exemple #1
0
class MapReaderPrivate():
    def tr(self, sourceText, disambiguation = '', n = -1):
        return QCoreApplication.translate('MapReader', sourceText, disambiguation, n)

    def trUtf8(self, sourceText, disambiguation = '', n = -1):
        return QCoreApplication.translate('MapReader', sourceText, disambiguation, n)

    def __init__(self, mapReader):
        self.p = mapReader
        self.mMap = None
        self.mError = QString('')
        self.mReadingExternalTileset = False
        self.xml = QXmlStreamReader()
        self.mGidMapper = GidMapper()

    def readMap(self, device, path):
        self.mError = QString('')
        self.mPath = path
        map = None
        self.xml.setDevice(device)
        if (self.xml.readNextStartElement() and self.xml.name() == "map"):
            map = self.__readMap()
        else:
            self.xml.raiseError(self.tr("Not a map file."))

        self.mGidMapper.clear()
        return map

    def readTileset(self, device, path):
        self.mError = ''
        self.mPath = path
        tileset = None
        self.mReadingExternalTileset = True
        self.xml.setDevice(device)
        if (self.xml.readNextStartElement() and self.xml.name() == "tileset"):
            tileset = self.__readTileset()
        else:
            self.xml.raiseError(self.tr("Not a tileset file."))
        self.mReadingExternalTileset = False
        return tileset

    def openFile(self, file):
        if (not file.exists()):
            self.mError = self.tr("File not found: %s"%file.fileName())
            return False
        elif (not file.open(QFile.ReadOnly | QFile.Text)):
            self.mError = self.tr("Unable to read file: %s"%file.fileName())
            return False

        return True

    def errorString(self):
        if self.mError != '':
            return self.mError
        else:
            return self.tr("%d\n\nLine %d, column %s"%(self.xml.lineNumber(), self.xml.columnNumber(), self.xml.errorString()))

    def __readUnknownElement(self):
        qDebug("Unknown element (fixme): "+self.xml.name()+" at line "+self.xml.lineNumber()+", column "+self.xml.columnNumber())
        self.xml.skipCurrentElement()

    def __readMap(self):
        atts = self.xml.attributes()
        mapWidth = Int(atts.value("width"))
        mapHeight = Int(atts.value("height"))
        tileWidth = Int(atts.value("tilewidth"))
        tileHeight = Int(atts.value("tileheight"))
        hexSideLength = Int(atts.value("hexsidelength"))
        orientationString = atts.value("orientation")
        orientation = orientationFromString(orientationString)
        if (orientation == Map.Orientation.Unknown):
            self.xml.raiseError(self.tr("Unsupported map orientation: \"%s\""%orientationString))

        staggerAxisString = atts.value("staggeraxis")
        staggerAxis = staggerAxisFromString(staggerAxisString)
        staggerIndexString = atts.value("staggerindex")
        staggerIndex = staggerIndexFromString(staggerIndexString)
        renderOrderString = atts.value("renderorder")
        renderOrder = renderOrderFromString(renderOrderString)
        nextObjectId = Int(atts.value("nextobjectid"))
        self.mMap = Map(orientation, mapWidth, mapHeight, tileWidth, tileHeight)
        self.mMap.setHexSideLength(hexSideLength)
        self.mMap.setStaggerAxis(staggerAxis)
        self.mMap.setStaggerIndex(staggerIndex)
        self.mMap.setRenderOrder(renderOrder)
        if (nextObjectId):
            self.mMap.setNextObjectId(nextObjectId)

        bgColorString = atts.value("backgroundcolor")
        if len(bgColorString)>0:
            self.mMap.setBackgroundColor(QColor(bgColorString))
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "properties"):
                self.mMap.mergeProperties(self.__readProperties())
            elif (self.xml.name() == "tileset"):
                self.mMap.addTileset(self.__readTileset())
            elif (self.xml.name() == "layer"):
                self.mMap.addLayer(self.__readLayer())
            elif (self.xml.name() == "objectgroup"):
                self.mMap.addLayer(self.__readObjectGroup())
            elif (self.xml.name() == "imagelayer"):
                self.mMap.addLayer(self.__readImageLayer())
            else:
                self.__readUnknownElement()

        # Clean up in case of error
        if (self.xml.hasError()):
            self.mMap = None

        return self.mMap

    def __readTileset(self):
        atts = self.xml.attributes()
        source = atts.value("source")
        firstGid = Int(atts.value("firstgid"))
        tileset = None
        if source == '': # Not an external tileset
            name = atts.value("name")
            tileWidth = Int(atts.value("tilewidth"))
            tileHeight = Int(atts.value("tileheight"))
            tileSpacing = Int(atts.value("spacing"))
            margin = Int(atts.value("margin"))
            if (tileWidth < 0 or tileHeight < 0
                or (firstGid == 0 and not self.mReadingExternalTileset)):
                self.xml.raiseError(self.tr("Invalid tileset parameters for tileset '%s'"%name))
            else:
                tileset = Tileset.create(name, tileWidth, tileHeight, tileSpacing, margin)

                while (self.xml.readNextStartElement()):
                    if (self.xml.name() == "tile"):
                        self.__readTilesetTile(tileset)
                    elif (self.xml.name() == "tileoffset"):
                        oa = self.xml.attributes()
                        x = Int(oa.value("x"))
                        y = Int(oa.value("y"))
                        tileset.setTileOffset(QPoint(x, y))
                        self.xml.skipCurrentElement()
                    elif (self.xml.name() == "properties"):
                        tileset.mergeProperties(self.__readProperties())
                    elif (self.xml.name() == "image"):
                        if (tileWidth == 0 or tileHeight == 0):
                            self.xml.raiseError(self.tr("Invalid tileset parameters for tileset '%s'"%name))
                            
                            tileset.clear()
                            break
                        else:
                            self.__readTilesetImage(tileset)
                    elif (self.xml.name() == "terraintypes"):
                        self.__readTilesetTerrainTypes(tileset)
                    else:
                        self.__readUnknownElement()
        else: # External tileset
            absoluteSource = self.p.resolveReference(source, self.mPath)
            tileset, error = self.p.readExternalTileset(absoluteSource)
            if (not tileset):
                self.xml.raiseError(self.tr("Error while loading tileset '%s': %s"%(absoluteSource, error)))

            self.xml.skipCurrentElement()

        if (tileset and not self.mReadingExternalTileset):
            self.mGidMapper.insert(firstGid, tileset)
        return tileset

    def __readTilesetTile(self, tileset):
        atts = self.xml.attributes()
        id = Int(atts.value("id"))
        if (id < 0):
            self.xml.raiseError(self.tr("Invalid tile ID: %d"%id))
            return

        hasImage = tileset.imageSource()!=''
        if (hasImage and id >= tileset.tileCount()):
            self.xml.raiseError(self.tr("Tile ID does not exist in tileset image: %d"%id))
            return

        if (id > tileset.tileCount()):
            self.xml.raiseError(self.tr("Invalid (nonconsecutive) tile ID: %d"%id))
            return

        # For tilesets without image source, consecutive tile IDs are allowed (for
        # tiles with individual images)
        if (id == tileset.tileCount()):
            tileset.addTile(QPixmap())
        tile = tileset.tileAt(id)
        # Read tile quadrant terrain ids
        terrain = atts.value("terrain")
        if terrain != '':
            quadrants = terrain.split(",")
            if (len(quadrants) == 4):
                for i in range(4):
                    if quadrants[i]=='':
                        t = -1
                    else:
                        t = Int(quadrants[i])
                    tile.setCornerTerrainId(i, t)

        # Read tile probability
        probability = atts.value("probability")
        if probability != '':
            tile.setProbability(Float(probability))
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "properties"):
                tile.mergeProperties(self.__readProperties())
            elif (self.xml.name() == "image"):
                source = self.xml.attributes().value("source")
                if source != '':
                    source = self.p.resolveReference(source, self.mPath)
                tileset.setTileImage(id, QPixmap.fromImage(self.__readImage()), source)
            elif (self.xml.name() == "objectgroup"):
                tile.setObjectGroup(self.__readObjectGroup())
            elif (self.xml.name() == "animation"):
                tile.setFrames(self.__readAnimationFrames())
            else:
                self.__readUnknownElement()

        # Temporary code to support TMW-style animation frame properties
        if (not tile.isAnimated() and tile.hasProperty("animation-frame0")):
            frames = QVector()
            i = 0
            while(i>=0):
                frameName = "animation-frame" + str(i)
                delayName = "animation-delay" + str(i)
                if (tile.hasProperty(frameName) and tile.hasProperty(delayName)):
                    frame = Frame()
                    frame.tileId = tile.property(frameName)
                    frame.duration = tile.property(delayName) * 10
                    frames.append(frame)
                else:
                    break
                i += 1

            tile.setFrames(frames)

    def __readTilesetImage(self, tileset):
        atts = self.xml.attributes()
        source = atts.value("source")
        trans = atts.value("trans")
        if len(trans)>0:
            if (not trans.startswith('#')):
                trans = '#' + trans
            tileset.setTransparentColor(QColor(trans))

        if len(source)>0:
            source = self.p.resolveReference(source, self.mPath)
        # Set the width that the tileset had when the map was saved
        width = Int(atts.value("width"))
        self.mGidMapper.setTilesetWidth(tileset, width)
        if (not tileset.loadFromImage(self.__readImage(), source)):
            self.xml.raiseError(self.tr("Error loading tileset image:\n'%s'"%source))

    def __readTilesetTerrainTypes(self, tileset):
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "terrain"):
                atts = self.xml.attributes()
                name = atts.value("name")
                tile = Int(atts.value("tile"))
                terrain = tileset.addTerrain(name, tile)
                while (self.xml.readNextStartElement()):
                    if (self.xml.name() == "properties"):
                        terrain.mergeProperties(self.__readProperties())
                    else:
                        self.__readUnknownElement()

            else:
                self.__readUnknownElement()

    def __readImage(self):
        atts = self.xml.attributes()
        source = atts.value("source")
        format = atts.value("format")
        if len(source)==0:
            while (self.xml.readNextStartElement()):
                if (self.xml.name() == "data"):
                    atts = self.xml.attributes()
                    encoding = atts.value("encoding")
                    data = self.xml.readElementText().toLatin1()
                    if (encoding == "base64"):
                        data = QByteArray.fromBase64(data)

                    self.xml.skipCurrentElement()
                    return QImage.fromData(data, format.toLatin1())
                else:
                    self.__readUnknownElement()

        else:
            self.xml.skipCurrentElement()
            source = self.p.resolveReference(source, self.mPath)
            image = self.p.readExternalImage(source)
            if (image.isNull()):
                self.xml.raiseError(self.tr("Error loading image:\n'%s'"%source))
            return image

        return QImage()

    def __readLayer(self):
        atts = self.xml.attributes()
        name = atts.value("name")
        x = Int(atts.value("x"))
        y = Int(atts.value("y"))
        width = Int(atts.value("width"))
        height = Int(atts.value("height"))
        tileLayer = TileLayer(name, x, y, width, height)
        readLayerAttributes(tileLayer, atts)
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "properties"):
                tileLayer.mergeProperties(self.__readProperties())
            elif (self.xml.name() == "data"):
                self.__readLayerData(tileLayer)
            else:
                self.__readUnknownElement()

        return tileLayer

    def __readLayerData(self, tileLayer):
        atts = self.xml.attributes()
        encoding = atts.value("encoding")
        compression = atts.value("compression")
        layerDataFormat = 0
        if (encoding == ''):
            layerDataFormat = Map.LayerDataFormat.XML
        elif (encoding == "csv"):
            layerDataFormat = Map.LayerDataFormat.CSV
        elif (encoding == "base64"):
            if (compression == ''):
                layerDataFormat = Map.LayerDataFormat.Base64
            elif (compression == "gzip"):
                layerDataFormat = Map.LayerDataFormat.Base64Gzip
            elif (compression == "zlib"):
                layerDataFormat = Map.LayerDataFormat.Base64Zlib
            else:
                self.xml.raiseError(self.tr("Compression method '%s' not supported"%compression))
                return
        else:
            self.xml.raiseError(self.tr("Unknown encoding: %s"%encoding))
            return
        
        self.mMap.setLayerDataFormat(layerDataFormat)
        
        x = 0
        y = 0
        while (self.xml.readNext() != QXmlStreamReader.Invalid):
            if (self.xml.isEndElement()):
                break
            elif (self.xml.isStartElement()):
                if (self.xml.name() == "tile"):
                    if (y >= tileLayer.height()):
                        self.xml.raiseError(self.tr("Too many <tile> elements"))
                        continue

                    atts = self.xml.attributes()
                    gid = Int(atts.value("gid"))
                    tileLayer.setCell(x, y, self.__cellForGid(gid))
                    x += 1
                    if (x >= tileLayer.width()):
                        x = 0
                        y += 1

                    self.xml.skipCurrentElement()
                else:
                    self.__readUnknownElement()
            elif (self.xml.isCharacters() and not self.xml.isWhitespace()):
                if (encoding == "base64"):
                    self.__decodeBinaryLayerData(tileLayer,
                                          self.xml.text(),
                                          layerDataFormat)
                elif (encoding == "csv"):
                    self.__decodeCSVLayerData(tileLayer, self.xml.text())

    def __decodeBinaryLayerData(self, tileLayer, data, format):
        error = self.mGidMapper.decodeLayerData(tileLayer, data, format)

        if error==DecodeError.CorruptLayerData:
            self.xml.raiseError(self.tr("Corrupt layer data for layer '%s'"%tileLayer.name()))
            return
        elif error==DecodeError.TileButNoTilesets:
            self.xml.raiseError(self.tr("Tile used but no tilesets specified"))
            return
        elif error==DecodeError.InvalidTile:
            self.xml.raiseError(self.tr("Invalid tile: %d"%self.mGidMapper.invalidTile()))
            return
        elif error==DecodeError.NoError:
            pass

    def __decodeCSVLayerData(self, tileLayer, text):
        trimText = text.strip()
        tiles = trimText.split(',')
        if (len(tiles) != tileLayer.width() * tileLayer.height()):
            self.xml.raiseError(self.tr("Corrupt layer data for layer '%s'"%tileLayer.name()))
            return

        for y in range(tileLayer.height()):
            for x in range(tileLayer.width()):
                conversionOk = False
                gid, conversionOk = Int2(tiles[y * tileLayer.width() + x])
                if (not conversionOk):
                    self.xml.raiseError(self.tr("Unable to parse tile at (%d,%d) on layer '%s'"%(x + 1, y + 1, tileLayer.name())))
                    return

                tileLayer.setCell(x, y, self.__cellForGid(gid))

    ##
    # Returns the cell for the given global tile ID. Errors are raised with
    # the QXmlStreamReader.
    #
    # @param gid the global tile ID
    # @return the cell data associated with the given global tile ID, or an
    #         empty cell if not found
    ##
    def __cellForGid(self, gid):
        ok = False
        result, ok = self.mGidMapper.gidToCell(gid)
        if (not ok):
            if (self.mGidMapper.isEmpty()):
                self.xml.raiseError(self.tr("Tile used but no tilesets specified"))
            else:
                self.xml.raiseError(self.tr("Invalid tile: %d"%gid))

        return result

    def __readImageLayer(self):
        atts = self.xml.attributes()
        name = atts.value("name")
        x = Int(atts.value("x"))
        y = Int(atts.value("y"))
        width = Int(atts.value("width"))
        height = Int(atts.value("height"))
        imageLayer = ImageLayer(name, x, y, width, height)
        readLayerAttributes(imageLayer, atts)
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "image"):
                self.__readImageLayerImage(imageLayer)
            elif (self.xml.name() == "properties"):
                imageLayer.mergeProperties(self.__readProperties())
            else:
                self.__readUnknownElement()

        return imageLayer

    def __readImageLayerImage(self, imageLayer):
        atts = self.xml.attributes()
        source = atts.value("source")
        trans = atts.value("trans")
        if trans != '':
            if (not trans.startswith('#')):
                trans = '#' + trans
            imageLayer.setTransparentColor(QColor(trans))

        source = self.p.resolveReference(source, self.mPath)
        imageLayerImage = self.p.readExternalImage(source)
        if (not imageLayer.loadFromImage(imageLayerImage, source)):
            self.xml.raiseError(self.tr("Error loading image layer image:\n'%s'"%source))
        self.xml.skipCurrentElement()

    def __readObjectGroup(self):
        atts = self.xml.attributes()
        name = atts.value("name")
        x = Int(atts.value("x"))
        y = Int(atts.value("y"))
        width = Int(atts.value("width"))
        height = Int(atts.value("height"))
        objectGroup = ObjectGroup(name, x, y, width, height)
        readLayerAttributes(objectGroup, atts)
        color = atts.value("color")
        if color != '':
            objectGroup.setColor(color)
        if (atts.hasAttribute("draworder")):
            value = atts.value("draworder")
            drawOrder = drawOrderFromString(value)
            if (drawOrder == ObjectGroup.DrawOrder.UnknownOrder):
                #del objectGroup
                self.xml.raiseError(self.tr("Invalid draw order: %s"%value))
                return None

            objectGroup.setDrawOrder(drawOrder)

        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "object"):
                objectGroup.addObject(self.__readObject())
            elif (self.xml.name() == "properties"):
                objectGroup.mergeProperties(self.__readProperties())
            else:
                self.__readUnknownElement()

        return objectGroup

    def __readObject(self):
        atts = self.xml.attributes()
        id = Int(atts.value("id"))
        name = atts.value("name")
        gid = Int(atts.value("gid"))
        x = Float(atts.value("x"))
        y = Float(atts.value("y"))
        width = Float(atts.value("width"))
        height = Float(atts.value("height"))
        type = atts.value("type")
        visibleRef = atts.value("visible")
        pos = QPointF(x, y)
        size = QSizeF(width, height)
        object = MapObject(name, type, pos, size)
        object.setId(id)

        try:
            rotation = Float(atts.value("rotation"))
            ok = True
        except:
            ok = False
        if (ok):
            object.setRotation(rotation)
        if (gid):
            object.setCell(self.__cellForGid(gid))
            if (not object.cell().isEmpty()):
                tileSize = object.cell().tile.size()
                if (width == 0):
                    object.setWidth(tileSize.width())
                if (height == 0):
                    object.setHeight(tileSize.height())
        
        try:
            visible = int(visibleRef)
            ok = True
        except:
            ok = False
        if ok:
            object.setVisible(visible)
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "properties"):
                object.mergeProperties(self.__readProperties())
            elif (self.xml.name() == "polygon"):
                object.setPolygon(self.__readPolygon())
                object.setShape(MapObject.Polygon)
            elif (self.xml.name() == "polyline"):
                object.setPolygon(self.__readPolygon())
                object.setShape(MapObject.Polyline)
            elif (self.xml.name() == "ellipse"):
                self.xml.skipCurrentElement()
                object.setShape(MapObject.Ellipse)
            else:
                self.__readUnknownElement()

        return object

    def __readPolygon(self):
        atts = self.xml.attributes()
        points = atts.value("points")
        pointsList = list(filter(lambda x:x.strip()!='', points.split(' ')))
        polygon = QPolygonF()
        ok = True
        for point in pointsList:
            try:
                x, y = point.split(',')
            except:
                ok = False
                break
            
            x, ok = Float2(x)
            if (not ok):
                break

            y, ok = Float2(y)
            if (not ok):
                break
            polygon.append(QPointF(x, y))

        if (not ok):
            self.xml.raiseError(self.tr("Invalid points data for polygon"))
        self.xml.skipCurrentElement()
        return polygon

    def __readAnimationFrames(self):
        frames = QVector()
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "frame"):
                atts = self.xml.attributes()
                frame = Frame()
                frame.tileId = Int(atts.value("tileid"))
                frame.duration = Int(atts.value("duration"))
                frames.append(frame)
                self.xml.skipCurrentElement()
            else:
                self.__readUnknownElement()

        return frames

    def __readProperties(self):
        properties = Properties()
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "property"):
                self.__readProperty(properties)
            else:
                self.__readUnknownElement()

        return properties

    def __readProperty(self, properties):
        atts = self.xml.attributes()
        propertyName = atts.value("name")
        propertyValue = atts.value("value")
        while (self.xml.readNext() != QXmlStreamReader.Invalid):
            if (self.xml.isEndElement()):
                break
            elif (self.xml.isCharacters() and not self.xml.isWhitespace()):
                if (propertyValue.isEmpty()):
                    propertyValue = self.xml.text()
            elif (self.xml.isStartElement()):
                self.__readUnknownElement()

        properties.insert(propertyName, propertyValue)
Exemple #2
0
class MapReaderPrivate():
    def tr(self, sourceText, disambiguation='', n=-1):
        return QCoreApplication.translate('MapReader', sourceText,
                                          disambiguation, n)

    def trUtf8(self, sourceText, disambiguation='', n=-1):
        return QCoreApplication.translate('MapReader', sourceText,
                                          disambiguation, n)

    def __init__(self, mapReader):
        self.p = mapReader
        self.mMap = None
        self.mError = QString('')
        self.mReadingExternalTileset = False
        self.xml = QXmlStreamReader()
        self.mGidMapper = GidMapper()

    def readMap(self, device, path):
        self.mError = QString('')
        self.mPath = path
        map = None
        self.xml.setDevice(device)
        if (self.xml.readNextStartElement() and self.xml.name() == "map"):
            map = self.__readMap()
        else:
            self.xml.raiseError(self.tr("Not a map file."))

        self.mGidMapper.clear()
        return map

    def readTileset(self, device, path):
        self.mError = ''
        self.mPath = path
        tileset = None
        self.mReadingExternalTileset = True
        self.xml.setDevice(device)
        if (self.xml.readNextStartElement() and self.xml.name() == "tileset"):
            tileset = self.__readTileset()
        else:
            self.xml.raiseError(self.tr("Not a tileset file."))
        self.mReadingExternalTileset = False
        return tileset

    def openFile(self, file):
        if (not file.exists()):
            self.mError = self.tr("File not found: %s" % file.fileName())
            return False
        elif (not file.open(QFile.ReadOnly | QFile.Text)):
            self.mError = self.tr("Unable to read file: %s" % file.fileName())
            return False

        return True

    def errorString(self):
        if self.mError != '':
            return self.mError
        else:
            return self.tr("%d\n\nLine %d, column %s" %
                           (self.xml.lineNumber(), self.xml.columnNumber(),
                            self.xml.errorString()))

    def __readUnknownElement(self):
        qDebug("Unknown element (fixme): " + self.xml.name() + " at line " +
               self.xml.lineNumber() + ", column " + self.xml.columnNumber())
        self.xml.skipCurrentElement()

    def __readMap(self):
        atts = self.xml.attributes()
        mapWidth = Int(atts.value("width"))
        mapHeight = Int(atts.value("height"))
        tileWidth = Int(atts.value("tilewidth"))
        tileHeight = Int(atts.value("tileheight"))
        hexSideLength = Int(atts.value("hexsidelength"))
        orientationString = atts.value("orientation")
        orientation = orientationFromString(orientationString)
        if (orientation == Map.Orientation.Unknown):
            self.xml.raiseError(
                self.tr("Unsupported map orientation: \"%s\"" %
                        orientationString))

        staggerAxisString = atts.value("staggeraxis")
        staggerAxis = staggerAxisFromString(staggerAxisString)
        staggerIndexString = atts.value("staggerindex")
        staggerIndex = staggerIndexFromString(staggerIndexString)
        renderOrderString = atts.value("renderorder")
        renderOrder = renderOrderFromString(renderOrderString)
        nextObjectId = Int(atts.value("nextobjectid"))
        self.mMap = Map(orientation, mapWidth, mapHeight, tileWidth,
                        tileHeight)
        self.mMap.setHexSideLength(hexSideLength)
        self.mMap.setStaggerAxis(staggerAxis)
        self.mMap.setStaggerIndex(staggerIndex)
        self.mMap.setRenderOrder(renderOrder)
        if (nextObjectId):
            self.mMap.setNextObjectId(nextObjectId)

        bgColorString = atts.value("backgroundcolor")
        if len(bgColorString) > 0:
            self.mMap.setBackgroundColor(QColor(bgColorString))
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "properties"):
                self.mMap.mergeProperties(self.__readProperties())
            elif (self.xml.name() == "tileset"):
                self.mMap.addTileset(self.__readTileset())
            elif (self.xml.name() == "layer"):
                self.mMap.addLayer(self.__readLayer())
            elif (self.xml.name() == "objectgroup"):
                self.mMap.addLayer(self.__readObjectGroup())
            elif (self.xml.name() == "imagelayer"):
                self.mMap.addLayer(self.__readImageLayer())
            else:
                self.__readUnknownElement()

        # Clean up in case of error
        if (self.xml.hasError()):
            self.mMap = None

        return self.mMap

    def __readTileset(self):
        atts = self.xml.attributes()
        source = atts.value("source")
        firstGid = Int(atts.value("firstgid"))
        tileset = None
        if source == '':  # Not an external tileset
            name = atts.value("name")
            tileWidth = Int(atts.value("tilewidth"))
            tileHeight = Int(atts.value("tileheight"))
            tileSpacing = Int(atts.value("spacing"))
            margin = Int(atts.value("margin"))
            if (tileWidth < 0 or tileHeight < 0
                    or (firstGid == 0 and not self.mReadingExternalTileset)):
                self.xml.raiseError(
                    self.tr("Invalid tileset parameters for tileset '%s'" %
                            name))
            else:
                tileset = Tileset.create(name, tileWidth, tileHeight,
                                         tileSpacing, margin)

                while (self.xml.readNextStartElement()):
                    if (self.xml.name() == "tile"):
                        self.__readTilesetTile(tileset)
                    elif (self.xml.name() == "tileoffset"):
                        oa = self.xml.attributes()
                        x = Int(oa.value("x"))
                        y = Int(oa.value("y"))
                        tileset.setTileOffset(QPoint(x, y))
                        self.xml.skipCurrentElement()
                    elif (self.xml.name() == "properties"):
                        tileset.mergeProperties(self.__readProperties())
                    elif (self.xml.name() == "image"):
                        if (tileWidth == 0 or tileHeight == 0):
                            self.xml.raiseError(
                                self.
                                tr("Invalid tileset parameters for tileset '%s'"
                                   % name))

                            tileset.clear()
                            break
                        else:
                            self.__readTilesetImage(tileset)
                    elif (self.xml.name() == "terraintypes"):
                        self.__readTilesetTerrainTypes(tileset)
                    else:
                        self.__readUnknownElement()
        else:  # External tileset
            absoluteSource = self.p.resolveReference(source, self.mPath)
            tileset, error = self.p.readExternalTileset(absoluteSource)
            if (not tileset):
                self.xml.raiseError(
                    self.tr("Error while loading tileset '%s': %s" %
                            (absoluteSource, error)))

            self.xml.skipCurrentElement()

        if (tileset and not self.mReadingExternalTileset):
            self.mGidMapper.insert(firstGid, tileset)
        return tileset

    def __readTilesetTile(self, tileset):
        atts = self.xml.attributes()
        id = Int(atts.value("id"))
        if (id < 0):
            self.xml.raiseError(self.tr("Invalid tile ID: %d" % id))
            return

        hasImage = tileset.imageSource() != ''
        if (hasImage and id >= tileset.tileCount()):
            self.xml.raiseError(
                self.tr("Tile ID does not exist in tileset image: %d" % id))
            return

        if (id > tileset.tileCount()):
            self.xml.raiseError(
                self.tr("Invalid (nonconsecutive) tile ID: %d" % id))
            return

        # For tilesets without image source, consecutive tile IDs are allowed (for
        # tiles with individual images)
        if (id == tileset.tileCount()):
            tileset.addTile(QPixmap())
        tile = tileset.tileAt(id)
        # Read tile quadrant terrain ids
        terrain = atts.value("terrain")
        if terrain != '':
            quadrants = terrain.split(",")
            if (len(quadrants) == 4):
                for i in range(4):
                    if quadrants[i] == '':
                        t = -1
                    else:
                        t = Int(quadrants[i])
                    tile.setCornerTerrainId(i, t)

        # Read tile probability
        probability = atts.value("probability")
        if probability != '':
            tile.setProbability(Float(probability))
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "properties"):
                tile.mergeProperties(self.__readProperties())
            elif (self.xml.name() == "image"):
                source = self.xml.attributes().value("source")
                if source != '':
                    source = self.p.resolveReference(source, self.mPath)
                tileset.setTileImage(id, QPixmap.fromImage(self.__readImage()),
                                     source)
            elif (self.xml.name() == "objectgroup"):
                tile.setObjectGroup(self.__readObjectGroup())
            elif (self.xml.name() == "animation"):
                tile.setFrames(self.__readAnimationFrames())
            else:
                self.__readUnknownElement()

        # Temporary code to support TMW-style animation frame properties
        if (not tile.isAnimated() and tile.hasProperty("animation-frame0")):
            frames = QVector()
            i = 0
            while (i >= 0):
                frameName = "animation-frame" + str(i)
                delayName = "animation-delay" + str(i)
                if (tile.hasProperty(frameName)
                        and tile.hasProperty(delayName)):
                    frame = Frame()
                    frame.tileId = tile.property(frameName)
                    frame.duration = tile.property(delayName) * 10
                    frames.append(frame)
                else:
                    break
                i += 1

            tile.setFrames(frames)

    def __readTilesetImage(self, tileset):
        atts = self.xml.attributes()
        source = atts.value("source")
        trans = atts.value("trans")
        if len(trans) > 0:
            if (not trans.startswith('#')):
                trans = '#' + trans
            tileset.setTransparentColor(QColor(trans))

        if len(source) > 0:
            source = self.p.resolveReference(source, self.mPath)
        # Set the width that the tileset had when the map was saved
        width = Int(atts.value("width"))
        self.mGidMapper.setTilesetWidth(tileset, width)
        if (not tileset.loadFromImage(self.__readImage(), source)):
            self.xml.raiseError(
                self.tr("Error loading tileset image:\n'%s'" % source))

    def __readTilesetTerrainTypes(self, tileset):
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "terrain"):
                atts = self.xml.attributes()
                name = atts.value("name")
                tile = Int(atts.value("tile"))
                terrain = tileset.addTerrain(name, tile)
                while (self.xml.readNextStartElement()):
                    if (self.xml.name() == "properties"):
                        terrain.mergeProperties(self.__readProperties())
                    else:
                        self.__readUnknownElement()

            else:
                self.__readUnknownElement()

    def __readImage(self):
        atts = self.xml.attributes()
        source = atts.value("source")
        format = atts.value("format")
        if len(source) == 0:
            while (self.xml.readNextStartElement()):
                if (self.xml.name() == "data"):
                    atts = self.xml.attributes()
                    encoding = atts.value("encoding")
                    data = self.xml.readElementText().toLatin1()
                    if (encoding == "base64"):
                        data = QByteArray.fromBase64(data)

                    self.xml.skipCurrentElement()
                    return QImage.fromData(data, format.toLatin1())
                else:
                    self.__readUnknownElement()

        else:
            self.xml.skipCurrentElement()
            source = self.p.resolveReference(source, self.mPath)
            image = self.p.readExternalImage(source)
            if (image.isNull()):
                self.xml.raiseError(
                    self.tr("Error loading image:\n'%s'" % source))
            return image

        return QImage()

    def __readLayer(self):
        atts = self.xml.attributes()
        name = atts.value("name")
        x = Int(atts.value("x"))
        y = Int(atts.value("y"))
        width = Int(atts.value("width"))
        height = Int(atts.value("height"))
        tileLayer = TileLayer(name, x, y, width, height)
        readLayerAttributes(tileLayer, atts)
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "properties"):
                tileLayer.mergeProperties(self.__readProperties())
            elif (self.xml.name() == "data"):
                self.__readLayerData(tileLayer)
            else:
                self.__readUnknownElement()

        return tileLayer

    def __readLayerData(self, tileLayer):
        atts = self.xml.attributes()
        encoding = atts.value("encoding")
        compression = atts.value("compression")
        layerDataFormat = 0
        if (encoding == ''):
            layerDataFormat = Map.LayerDataFormat.XML
        elif (encoding == "csv"):
            layerDataFormat = Map.LayerDataFormat.CSV
        elif (encoding == "base64"):
            if (compression == ''):
                layerDataFormat = Map.LayerDataFormat.Base64
            elif (compression == "gzip"):
                layerDataFormat = Map.LayerDataFormat.Base64Gzip
            elif (compression == "zlib"):
                layerDataFormat = Map.LayerDataFormat.Base64Zlib
            else:
                self.xml.raiseError(
                    self.tr("Compression method '%s' not supported" %
                            compression))
                return
        else:
            self.xml.raiseError(self.tr("Unknown encoding: %s" % encoding))
            return

        self.mMap.setLayerDataFormat(layerDataFormat)

        x = 0
        y = 0
        while (self.xml.readNext() != QXmlStreamReader.Invalid):
            if (self.xml.isEndElement()):
                break
            elif (self.xml.isStartElement()):
                if (self.xml.name() == "tile"):
                    if (y >= tileLayer.height()):
                        self.xml.raiseError(
                            self.tr("Too many <tile> elements"))
                        continue

                    atts = self.xml.attributes()
                    gid = Int(atts.value("gid"))
                    tileLayer.setCell(x, y, self.__cellForGid(gid))
                    x += 1
                    if (x >= tileLayer.width()):
                        x = 0
                        y += 1

                    self.xml.skipCurrentElement()
                else:
                    self.__readUnknownElement()
            elif (self.xml.isCharacters() and not self.xml.isWhitespace()):
                if (encoding == "base64"):
                    self.__decodeBinaryLayerData(tileLayer, self.xml.text(),
                                                 layerDataFormat)
                elif (encoding == "csv"):
                    self.__decodeCSVLayerData(tileLayer, self.xml.text())

    def __decodeBinaryLayerData(self, tileLayer, data, format):
        error = self.mGidMapper.decodeLayerData(tileLayer, data, format)

        if error == DecodeError.CorruptLayerData:
            self.xml.raiseError(
                self.tr("Corrupt layer data for layer '%s'" %
                        tileLayer.name()))
            return
        elif error == DecodeError.TileButNoTilesets:
            self.xml.raiseError(self.tr("Tile used but no tilesets specified"))
            return
        elif error == DecodeError.InvalidTile:
            self.xml.raiseError(
                self.tr("Invalid tile: %d" % self.mGidMapper.invalidTile()))
            return
        elif error == DecodeError.NoError:
            pass

    def __decodeCSVLayerData(self, tileLayer, text):
        trimText = text.strip()
        tiles = trimText.split(',')
        if (len(tiles) != tileLayer.width() * tileLayer.height()):
            self.xml.raiseError(
                self.tr("Corrupt layer data for layer '%s'" %
                        tileLayer.name()))
            return

        for y in range(tileLayer.height()):
            for x in range(tileLayer.width()):
                conversionOk = False
                gid, conversionOk = Int2(tiles[y * tileLayer.width() + x])
                if (not conversionOk):
                    self.xml.raiseError(
                        self.tr(
                            "Unable to parse tile at (%d,%d) on layer '%s'" %
                            (x + 1, y + 1, tileLayer.name())))
                    return

                tileLayer.setCell(x, y, self.__cellForGid(gid))

    ##
    # Returns the cell for the given global tile ID. Errors are raised with
    # the QXmlStreamReader.
    #
    # @param gid the global tile ID
    # @return the cell data associated with the given global tile ID, or an
    #         empty cell if not found
    ##
    def __cellForGid(self, gid):
        ok = False
        result, ok = self.mGidMapper.gidToCell(gid)
        if (not ok):
            if (self.mGidMapper.isEmpty()):
                self.xml.raiseError(
                    self.tr("Tile used but no tilesets specified"))
            else:
                self.xml.raiseError(self.tr("Invalid tile: %d" % gid))

        return result

    def __readImageLayer(self):
        atts = self.xml.attributes()
        name = atts.value("name")
        x = Int(atts.value("x"))
        y = Int(atts.value("y"))
        width = Int(atts.value("width"))
        height = Int(atts.value("height"))
        imageLayer = ImageLayer(name, x, y, width, height)
        readLayerAttributes(imageLayer, atts)
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "image"):
                self.__readImageLayerImage(imageLayer)
            elif (self.xml.name() == "properties"):
                imageLayer.mergeProperties(self.__readProperties())
            else:
                self.__readUnknownElement()

        return imageLayer

    def __readImageLayerImage(self, imageLayer):
        atts = self.xml.attributes()
        source = atts.value("source")
        trans = atts.value("trans")
        if trans != '':
            if (not trans.startswith('#')):
                trans = '#' + trans
            imageLayer.setTransparentColor(QColor(trans))

        source = self.p.resolveReference(source, self.mPath)
        imageLayerImage = self.p.readExternalImage(source)
        if (not imageLayer.loadFromImage(imageLayerImage, source)):
            self.xml.raiseError(
                self.tr("Error loading image layer image:\n'%s'" % source))
        self.xml.skipCurrentElement()

    def __readObjectGroup(self):
        atts = self.xml.attributes()
        name = atts.value("name")
        x = Int(atts.value("x"))
        y = Int(atts.value("y"))
        width = Int(atts.value("width"))
        height = Int(atts.value("height"))
        objectGroup = ObjectGroup(name, x, y, width, height)
        readLayerAttributes(objectGroup, atts)
        color = atts.value("color")
        if color != '':
            objectGroup.setColor(color)
        if (atts.hasAttribute("draworder")):
            value = atts.value("draworder")
            drawOrder = drawOrderFromString(value)
            if (drawOrder == ObjectGroup.DrawOrder.UnknownOrder):
                #del objectGroup
                self.xml.raiseError(self.tr("Invalid draw order: %s" % value))
                return None

            objectGroup.setDrawOrder(drawOrder)

        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "object"):
                objectGroup.addObject(self.__readObject())
            elif (self.xml.name() == "properties"):
                objectGroup.mergeProperties(self.__readProperties())
            else:
                self.__readUnknownElement()

        return objectGroup

    def __readObject(self):
        atts = self.xml.attributes()
        id = Int(atts.value("id"))
        name = atts.value("name")
        gid = Int(atts.value("gid"))
        x = Float(atts.value("x"))
        y = Float(atts.value("y"))
        width = Float(atts.value("width"))
        height = Float(atts.value("height"))
        type = atts.value("type")
        visibleRef = atts.value("visible")
        pos = QPointF(x, y)
        size = QSizeF(width, height)
        object = MapObject(name, type, pos, size)
        object.setId(id)

        try:
            rotation = Float(atts.value("rotation"))
            ok = True
        except:
            ok = False
        if (ok):
            object.setRotation(rotation)
        if (gid):
            object.setCell(self.__cellForGid(gid))
            if (not object.cell().isEmpty()):
                tileSize = object.cell().tile.size()
                if (width == 0):
                    object.setWidth(tileSize.width())
                if (height == 0):
                    object.setHeight(tileSize.height())

        try:
            visible = int(visibleRef)
            ok = True
        except:
            ok = False
        if ok:
            object.setVisible(visible)
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "properties"):
                object.mergeProperties(self.__readProperties())
            elif (self.xml.name() == "polygon"):
                object.setPolygon(self.__readPolygon())
                object.setShape(MapObject.Polygon)
            elif (self.xml.name() == "polyline"):
                object.setPolygon(self.__readPolygon())
                object.setShape(MapObject.Polyline)
            elif (self.xml.name() == "ellipse"):
                self.xml.skipCurrentElement()
                object.setShape(MapObject.Ellipse)
            else:
                self.__readUnknownElement()

        return object

    def __readPolygon(self):
        atts = self.xml.attributes()
        points = atts.value("points")
        pointsList = list(filter(lambda x: x.strip() != '', points.split(' ')))
        polygon = QPolygonF()
        ok = True
        for point in pointsList:
            try:
                x, y = point.split(',')
            except:
                ok = False
                break

            x, ok = Float2(x)
            if (not ok):
                break

            y, ok = Float2(y)
            if (not ok):
                break
            polygon.append(QPointF(x, y))

        if (not ok):
            self.xml.raiseError(self.tr("Invalid points data for polygon"))
        self.xml.skipCurrentElement()
        return polygon

    def __readAnimationFrames(self):
        frames = QVector()
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "frame"):
                atts = self.xml.attributes()
                frame = Frame()
                frame.tileId = Int(atts.value("tileid"))
                frame.duration = Int(atts.value("duration"))
                frames.append(frame)
                self.xml.skipCurrentElement()
            else:
                self.__readUnknownElement()

        return frames

    def __readProperties(self):
        properties = Properties()
        while (self.xml.readNextStartElement()):
            if (self.xml.name() == "property"):
                self.__readProperty(properties)
            else:
                self.__readUnknownElement()

        return properties

    def __readProperty(self, properties):
        atts = self.xml.attributes()
        propertyName = atts.value("name")
        propertyValue = atts.value("value")
        while (self.xml.readNext() != QXmlStreamReader.Invalid):
            if (self.xml.isEndElement()):
                break
            elif (self.xml.isCharacters() and not self.xml.isWhitespace()):
                if (propertyValue.isEmpty()):
                    propertyValue = self.xml.text()
            elif (self.xml.isStartElement()):
                self.__readUnknownElement()

        properties.insert(propertyName, propertyValue)
class VariantToMapConverter():
    def __init__(self):
        self.mError = ''
        self.mGidMapper = GidMapper()
        self.mMap = None
        self.mMapDir = QDir()
        self.mReadingExternalTileset = False

    # Using the MapReader context since the messages are the same
    def tr(self, sourceText, disambiguation='', n=-1):
        return QCoreApplication.translate('MapReader', sourceText,
                                          disambiguation, n)

    def trUtf8(self, sourceText, disambiguation='', n=-1):
        return QCoreApplication.translate('MapReader', sourceText,
                                          disambiguation, n)

    ##
    # Tries to convert the given \a variant to a Map instance. The \a mapDir
    # is necessary to resolve any relative references to external images.
    #
    # Returns 0 in case of an error. The error can be obstained using
    # self.errorString().
    ##
    def toMap(self, variant, mapDir):
        self.mGidMapper.clear()
        self.mMapDir = mapDir
        variantMap = variant
        orientationString = variantMap.get("orientation", '')
        orientation = orientationFromString(orientationString)
        if (orientation == Map.Orientation.Unknown):
            self.mError = self.tr("Unsupported map orientation: \"%s\"" %
                                  orientationString)
            return None

        staggerAxisString = variantMap.get("staggeraxis", '')
        staggerAxis = staggerAxisFromString(staggerAxisString)
        staggerIndexString = variantMap.get("staggerindex", '')
        staggerIndex = staggerIndexFromString(staggerIndexString)
        renderOrderString = variantMap.get("renderorder", '')
        renderOrder = renderOrderFromString(renderOrderString)
        nextObjectId = variantMap.get("nextobjectid", 0)
        map = Map(orientation, variantMap.get("width", 0),
                  variantMap.get("height", 0), variantMap.get("tilewidth", 0),
                  variantMap.get("tileheight", 0))
        map.setHexSideLength(variantMap.get("hexsidelength", 0))
        map.setStaggerAxis(staggerAxis)
        map.setStaggerIndex(staggerIndex)
        map.setRenderOrder(renderOrder)
        if (nextObjectId):
            map.setNextObjectId(nextObjectId)
        self.mMap = map
        map.setProperties(self.toProperties(variantMap.get("properties", {})))
        bgColor = variantMap.get("backgroundcolor", '')
        if (bgColor != '' and QColor.isValidColor(bgColor)):
            map.setBackgroundColor(QColor(bgColor))
        for tilesetVariant in variantMap.get("tilesets", []):
            tileset = self.__toTileset(tilesetVariant)
            if not tileset:
                return None

            map.addTileset(tileset)

        for layerVariant in variantMap.get("layers", []):
            layer = self.toLayer(layerVariant)
            if not layer:
                return None

            map.addLayer(layer)

        return map

    ##
    # Returns the last error, if any.
    ##
    def errorString(self):
        return self.mError

    def toProperties(self, variant):
        variantMap = variant
        properties = Properties()
        for it in variantMap.items():
            properties[it[0]] = it[1]
        return properties

    def toTileset(self, variant, directory):
        self.mMapDir = directory
        self.mReadingExternalTileset = True
        tileset = self.__toTileset(variant)
        self.mReadingExternalTileset = False
        return tileset

    def __toTileset(self, variant):
        variantMap = variant
        firstGid = variantMap.get("firstgid", 0)

        # Handle external tilesets
        sourceVariant = variantMap.get("source", '')
        if sourceVariant != '':
            source = resolvePath(self.mMapDir, sourceVariant)
            tileset, error = readTileset(source)
            if not tileset:
                self.mError = self.tr("Error while loading tileset '%s': %s" %
                                      (source, error))
            else:
                self.mGidMapper.insert(firstGid, tileset)
            return tileset

        name = variantMap.get("name", '')
        tileWidth = variantMap.get("tilewidth", 0)
        tileHeight = variantMap.get("tileheight", 0)
        spacing = variantMap.get("spacing", 0)
        margin = variantMap.get("margin", 0)
        tileOffset = variantMap.get("tileoffset", {})
        tileOffsetX = tileOffset.get("x", 0)
        tileOffsetY = tileOffset.get("y", 0)
        if (tileWidth <= 0 or tileHeight <= 0
                or (firstGid == 0 and not self.mReadingExternalTileset)):
            self.mError = self.tr(
                "Invalid tileset parameters for tileset '%s'" % name)
            return None

        tileset = Tileset.create(name, tileWidth, tileHeight, spacing, margin)
        tileset.setTileOffset(QPoint(tileOffsetX, tileOffsetY))
        trans = variantMap.get("transparentcolor", '')
        if (trans != '' and QColor.isValidColor(trans)):
            tileset.setTransparentColor(QColor(trans))
        imageVariant = variantMap.get("image", '')
        if imageVariant != '':
            imagePath = resolvePath(self.mMapDir, imageVariant)
            if (not tileset.loadFromImage(imagePath)):
                self.mError = self.tr("Error loading tileset image:\n'%s'" %
                                      imagePath)
                return None

        tileset.setProperties(
            self.toProperties(variantMap.get("properties", {})))

        # Read terrains
        terrainsVariantList = variantMap.get("terrains", [])
        for terrainMap in terrainsVariantList:
            tileset.addTerrain(terrainMap.get("name", ''),
                               terrainMap.get("tile", 0))

        # Read tile terrain and external image information
        tilesVariantMap = variantMap.get("tiles", {})
        for it in tilesVariantMap.items():
            ok = False
            tileIndex = Int(it[0])
            if (tileIndex < 0):
                self.mError = self.tr("Tileset tile index negative:\n'%d'" %
                                      tileIndex)

            if (tileIndex >= tileset.tileCount()):
                # Extend the tileset to fit the tile
                if (tileIndex >= len(tilesVariantMap)):
                    # If tiles are  defined this way, there should be an entry
                    # for each tile.
                    # Limit the index to number of entries to prevent running out
                    # of memory on malicious input.f
                    self.mError = self.tr(
                        "Tileset tile index too high:\n'%d'" % tileIndex)
                    return None

                for i in range(tileset.tileCount(), tileIndex + 1):
                    tileset.addTile(QPixmap())

            tile = tileset.tileAt(tileIndex)
            if (tile):
                tileVar = it[1]
                terrains = tileVar.get("terrain", [])
                if len(terrains) == 4:
                    for i in range(0, 4):
                        terrainId, ok = Int2(terrains[i])
                        if (ok and terrainId >= 0
                                and terrainId < tileset.terrainCount()):
                            tile.setCornerTerrainId(i, terrainId)

                probability, ok = Float2(tileVar.get("probability", 0.0))
                if (ok):
                    tile.setProbability(probability)
                imageVariant = tileVar.get("image", '')
                if imageVariant != '':
                    imagePath = resolvePath(self.mMapDir, imageVariant)
                    tileset.setTileImage(tileIndex, QPixmap(imagePath),
                                         imagePath)

                objectGroupVariant = tileVar.get("objectgroup", {})
                if len(objectGroupVariant) > 0:
                    tile.setObjectGroup(self.toObjectGroup(objectGroupVariant))
                frameList = tileVar.get("animation", [])
                lenFrames = len(frameList)
                if lenFrames > 0:
                    frames = QVector()
                    for i in range(lenFrames):
                        frames.append(Frame())
                    for i in range(lenFrames - 1, -1, -1):
                        frameVariantMap = frameList[i]
                        frame = frames[i]
                        frame.tileId = frameVariantMap.get("tileid", 0)
                        frame.duration = frameVariantMap.get("duration", 0)

                    tile.setFrames(frames)

        # Read tile properties
        propertiesVariantMap = variantMap.get("tileproperties", {})

        for it in propertiesVariantMap.items():
            tileIndex = Int(it[0])
            propertiesVar = it[1]
            if (tileIndex >= 0 and tileIndex < tileset.tileCount()):
                properties = self.toProperties(propertiesVar)
                tileset.tileAt(tileIndex).setProperties(properties)

        if not self.mReadingExternalTileset:
            self.mGidMapper.insert(firstGid, tileset)

        return tileset

    def toLayer(self, variant):
        variantMap = variant
        layer = None
        if (variantMap["type"] == "tilelayer"):
            layer = self.toTileLayer(variantMap)
        elif (variantMap["type"] == "objectgroup"):
            layer = self.toObjectGroup(variantMap)
        elif (variantMap["type"] == "imagelayer"):
            layer = self.toImageLayer(variantMap)
        if (layer):
            layer.setProperties(
                self.toProperties(variantMap.get("properties", {})))
            offset = QPointF(variantMap.get("offsetx", 0.0),
                             variantMap.get("offsety", 0.0))
            layer.setOffset(offset)

        return layer

    def toTileLayer(self, variantMap):
        name = variantMap.get("name", '')
        width = variantMap.get("width", 0)
        height = variantMap.get("height", 0)
        dataVariant = variantMap["data"]

        tileLayer = TileLayer(name, variantMap.get("x", 0),
                              variantMap.get("y", 0), width, height)
        opacity = variantMap.get("opacity", 0.0)
        visible = variantMap.get("visible", True)
        tileLayer.setOpacity(opacity)
        tileLayer.setVisible(visible)

        encoding = variantMap.get("encoding", '')
        compression = variantMap.get("compression", '')

        if encoding == '' or encoding == "csv":
            layerDataFormat = Map.LayerDataFormat.CSV
        elif (encoding == "base64"):
            if compression == '':
                layerDataFormat = Map.LayerDataFormat.Base64
            elif (compression == "gzip"):
                layerDataFormat = Map.LayerDataFormat.Base64Gzip
            elif (compression == "zlib"):
                layerDataFormat = Map.LayerDataFormat.Base64Zlib
            else:
                self.mError = self.tr("Compression method '%s' not supported" %
                                      compression)
                return None
        else:
            self.mError = self.tr("Unknown encoding: %1" % encoding)
            return None

        self.mMap.setLayerDataFormat(layerDataFormat)

        if layerDataFormat == Map.LayerDataFormat.XML or layerDataFormat == Map.LayerDataFormat.CSV:
            dataVariantList = dataVariant

            if len(dataVariantList) != width * height:
                self.mError = self.tr("Corrupt layer data for layer '%s'" %
                                      name)
                return None
            x = 0
            y = 0
            ok = False

            if (len(dataVariantList) != width * height):
                self.mError = self.tr("Corrupt layer data for layer '%s'" %
                                      name)
                return None
            for gidVariant in dataVariantList:
                gid, ok = Int2(gidVariant)
                if (not ok):
                    self.mError = self.tr(
                        "Unable to parse tile at (%d,%d) on layer '%s'" %
                        (x, y, tileLayer.name()))
                    return None

                cell = self.mGidMapper.gidToCell(gid, ok)
                tileLayer.setCell(x, y, cell)
                x += 1
                if (x >= tileLayer.width()):
                    x = 0
                    y += 1
        elif layerDataFormat==Map.LayerDataFormat.Base64 \
            or layerDataFormat==Map.LayerDataFormat.Base64Zlib \
            or layerDataFormat==Map.LayerDataFormat.Base64Gzip:
            data = QByteArray(dataVariant)
            error = self.mGidMapper.decodeLayerData(tileLayer, data,
                                                    layerDataFormat)

            if error == DecodeError.CorruptLayerData:
                self.mError = self.tr("Corrupt layer data for layer '%s'" %
                                      name)
                return None
            elif error == DecodeError.TileButNoTilesets:
                self.mError = self.tr("Tile used but no tilesets specified")
                return None
            elif error == DecodeError.InvalidTile:
                self.mError = self.tr("Invalid tile: %d" %
                                      self.mGidMapper.invalidTile())
                return None
            elif error == DecodeError.NoError:
                pass

        return tileLayer

    def toObjectGroup(self, variantMap):
        objectGroup = ObjectGroup(variantMap.get("name", ''),
                                  variantMap.get("x", 0),
                                  variantMap.get("y", 0),
                                  variantMap.get("width", 0),
                                  variantMap.get("height", 0))
        opacity = variantMap.get("opacity", 0.0)
        visible = variantMap.get("visible", True)
        objectGroup.setOpacity(opacity)
        objectGroup.setVisible(visible)
        objectGroup.setColor(variantMap.get("color", ''))
        drawOrderString = variantMap.get("draworder", '')
        if drawOrderString != '':
            objectGroup.setDrawOrder(drawOrderFromString(drawOrderString))
            if (objectGroup.drawOrder() == ObjectGroup.DrawOrder.UnknownOrder):
                self.mError = self.tr("Invalid draw order: %s" %
                                      drawOrderString)
                return None

        for objectVariant in variantMap.get("objects", []):
            objectVariantMap = objectVariant
            name = objectVariantMap.get("name", '')
            type = objectVariantMap.get("type", '')
            id = objectVariantMap.get("id", 0.0)
            gid = objectVariantMap.get("gid", 0.0)
            x = objectVariantMap.get("x", 0.0)
            y = objectVariantMap.get("y", 0.0)
            width = objectVariantMap.get("width", 0.0)
            height = objectVariantMap.get("height", 0.0)
            rotation = objectVariantMap.get("rotation", 0.0)
            pos = QPointF(x, y)
            size = QSizeF(width, height)
            object = MapObject(name, type, pos, size)
            object.setId(id)
            object.setRotation(rotation)
            if (gid):
                cell, ok = self.mGidMapper.gidToCell(gid)
                object.setCell(cell)
                if not object.cell().isEmpty():
                    tileSize = object.cell().tile.size()
                    if width == 0:
                        object.setWidth(tileSize.width())
                    if height == 0:
                        object.setHeight(tileSize.height())

            if (objectVariantMap.__contains__("visible")):
                object.setVisible(objectVariantMap.get("visible", True))
            object.setProperties(
                self.toProperties(objectVariantMap.get("properties", {})))
            objectGroup.addObject(object)
            polylineVariant = objectVariantMap.get("polyline", [])
            polygonVariant = objectVariantMap.get("polygon", [])
            if len(polygonVariant) > 0:
                object.setShape(MapObject.Polygon)
                object.setPolygon(self.toPolygon(polygonVariant))

            if len(polylineVariant) > 0:
                object.setShape(MapObject.Polyline)
                object.setPolygon(self.toPolygon(polylineVariant))

            if (objectVariantMap.__contains__("ellipse")):
                object.setShape(MapObject.Ellipse)

        return objectGroup

    def toImageLayer(self, variantMap):
        imageLayer = ImageLayer(variantMap.get("name", ''),
                                variantMap.get("x", 0), variantMap.get("y", 0),
                                variantMap.get("width", 0),
                                variantMap.get("height", 0))
        opacity = variantMap.get("opacity", 0.0)
        visible = variantMap.get("visible", True)
        imageLayer.setOpacity(opacity)
        imageLayer.setVisible(visible)
        trans = variantMap.get("transparentcolor", '')
        if (not trans.isEmpty() and QColor.isValidColor(trans)):
            imageLayer.setTransparentColor(QColor(trans))
        imageVariant = variantMap.get("image", '')
        if imageVariant != '':
            imagePath = resolvePath(self.mMapDir, imageVariant)
            if (not imageLayer.loadFromImage(QImage(imagePath), imagePath)):
                self.mError = self.tr("Error loading image:\n'%s'" % imagePath)
                return None

        return imageLayer

    def toPolygon(self, variant):
        polygon = QPolygonF()
        for pointVariant in variant:
            pointVariantMap = pointVariant
            pointX = pointVariantMap.get("x", 0.0)
            pointY = pointVariantMap.get("y", 0.0)
            polygon.append(QPointF(pointX, pointY))

        return polygon
class VariantToMapConverter():

    def __init__(self):
        self.mError = ''
        self.mGidMapper = GidMapper()
        self.mMap = None
        self.mMapDir = QDir()
        self.mReadingExternalTileset = False

    # Using the MapReader context since the messages are the same
    def tr(self, sourceText, disambiguation = '', n = -1):
        return QCoreApplication.translate('MapReader', sourceText, disambiguation, n)

    def trUtf8(self, sourceText, disambiguation = '', n = -1):
        return QCoreApplication.translate('MapReader', sourceText, disambiguation, n)
        
    ##
    # Tries to convert the given \a variant to a Map instance. The \a mapDir
    # is necessary to resolve any relative references to external images.
    #
    # Returns 0 in case of an error. The error can be obstained using
    # self.errorString().
    ##
    def toMap(self, variant, mapDir):
        self.mGidMapper.clear()
        self.mMapDir = mapDir
        variantMap = variant
        orientationString = variantMap.get("orientation", '')
        orientation = orientationFromString(orientationString)
        if (orientation == Map.Orientation.Unknown):
            self.mError = self.tr("Unsupported map orientation: \"%s\""%orientationString)
            return None
        
        staggerAxisString = variantMap.get("staggeraxis", '')
        staggerAxis = staggerAxisFromString(staggerAxisString)
        staggerIndexString = variantMap.get("staggerindex", '')
        staggerIndex = staggerIndexFromString(staggerIndexString)
        renderOrderString = variantMap.get("renderorder", '')
        renderOrder = renderOrderFromString(renderOrderString)
        nextObjectId = variantMap.get("nextobjectid", 0)
        map = Map(orientation,
                           variantMap.get("width",0),
                           variantMap.get("height",0),
                           variantMap.get("tilewidth",0),
                           variantMap.get("tileheight",0))
        map.setHexSideLength(variantMap.get("hexsidelength", 0))
        map.setStaggerAxis(staggerAxis)
        map.setStaggerIndex(staggerIndex)
        map.setRenderOrder(renderOrder)
        if (nextObjectId):
            map.setNextObjectId(nextObjectId)
        self.mMap = map
        map.setProperties(self.toProperties(variantMap.get("properties", {})))
        bgColor = variantMap.get("backgroundcolor", '')
        if (bgColor!='' and QColor.isValidColor(bgColor)):
            map.setBackgroundColor(QColor(bgColor))
        for tilesetVariant in variantMap.get("tilesets", []):
            tileset = self.__toTileset(tilesetVariant)
            if not tileset:
                return None
            
            map.addTileset(tileset)
        
        for layerVariant in variantMap.get("layers", []):
            layer = self.toLayer(layerVariant)
            if not layer:
                return None
            
            map.addLayer(layer)
        
        return map
        
    ##
    # Returns the last error, if any.
    ##
    def errorString(self):
        return self.mError

    def toProperties(self, variant):
        variantMap = variant
        properties = Properties()
        for it in variantMap.items():
            properties[it[0]] = it[1]
        return properties
    
    def toTileset(self, variant, directory):
        self.mMapDir = directory
        self.mReadingExternalTileset = True
        tileset = self.__toTileset(variant)
        self.mReadingExternalTileset = False
        return tileset

    def __toTileset(self, variant):
        variantMap = variant
        firstGid = variantMap.get("firstgid",0)
        
        # Handle external tilesets
        sourceVariant = variantMap.get("source", '')
        if sourceVariant != '':
            source = resolvePath(self.mMapDir, sourceVariant)
            tileset, error = readTileset(source)
            if not tileset:
                self.mError = self.tr("Error while loading tileset '%s': %s"%(source, error))
            else:
                self.mGidMapper.insert(firstGid, tileset)
            return tileset

        name = variantMap.get("name",'')
        tileWidth = variantMap.get("tilewidth",0)
        tileHeight = variantMap.get("tileheight",0)
        spacing = variantMap.get("spacing",0)
        margin = variantMap.get("margin",0)
        tileOffset = variantMap.get("tileoffset", {})
        tileOffsetX = tileOffset.get("x",0)
        tileOffsetY = tileOffset.get("y",0)
        if (tileWidth <= 0 or tileHeight <= 0 or (firstGid == 0 and not self.mReadingExternalTileset)):
            self.mError = self.tr("Invalid tileset parameters for tileset '%s'"%name)
            return None
        
        tileset = Tileset.create(name, tileWidth, tileHeight, spacing, margin)
        tileset.setTileOffset(QPoint(tileOffsetX, tileOffsetY))
        trans = variantMap.get("transparentcolor", '')
        if (trans!='' and QColor.isValidColor(trans)):
            tileset.setTransparentColor(QColor(trans))
        imageVariant = variantMap.get("image",'')
        if imageVariant != '':
            imagePath = resolvePath(self.mMapDir, imageVariant)
            if (not tileset.loadFromImage(imagePath)):
                self.mError = self.tr("Error loading tileset image:\n'%s'"%imagePath)
                return None

        tileset.setProperties(self.toProperties(variantMap.get("properties", {})))
        
        # Read terrains
        terrainsVariantList = variantMap.get("terrains", [])
        for terrainMap in terrainsVariantList:
            tileset.addTerrain(terrainMap.get("name", ''), terrainMap.get("tile", 0))

        # Read tile terrain and external image information
        tilesVariantMap = variantMap.get("tiles", {})
        for it in tilesVariantMap.items():
            ok = False
            tileIndex = Int(it[0])
            if (tileIndex < 0):
                self.mError = self.tr("Tileset tile index negative:\n'%d'"%tileIndex)
            
            if (tileIndex >= tileset.tileCount()):
                # Extend the tileset to fit the tile
                if (tileIndex >= len(tilesVariantMap)):
                    # If tiles are  defined this way, there should be an entry
                    # for each tile.
                    # Limit the index to number of entries to prevent running out
                    # of memory on malicious input.f
                    self.mError = self.tr("Tileset tile index too high:\n'%d'"%tileIndex)
                    return None
                
                for i in range(tileset.tileCount(), tileIndex+1):
                    tileset.addTile(QPixmap())
            
            tile = tileset.tileAt(tileIndex)
            if (tile):
                tileVar = it[1]
                terrains = tileVar.get("terrain", [])
                if len(terrains) == 4:
                    for i in range(0, 4):
                        terrainId, ok = Int2(terrains[i])
                        if (ok and terrainId >= 0 and terrainId < tileset.terrainCount()):
                            tile.setCornerTerrainId(i, terrainId)

                probability, ok = Float2(tileVar.get("probability", 0.0))
                if (ok):
                    tile.setProbability(probability)
                imageVariant = tileVar.get("image",'')
                if imageVariant != '':
                    imagePath = resolvePath(self.mMapDir, imageVariant)
                    tileset.setTileImage(tileIndex, QPixmap(imagePath), imagePath)
                
                objectGroupVariant = tileVar.get("objectgroup", {})
                if len(objectGroupVariant) > 0:
                    tile.setObjectGroup(self.toObjectGroup(objectGroupVariant))
                frameList = tileVar.get("animation", [])
                lenFrames = len(frameList)
                if lenFrames > 0:
                    frames = QVector()
                    for i in range(lenFrames):
                        frames.append(Frame())
                    for i in range(lenFrames - 1, -1, -1):
                        frameVariantMap = frameList[i]
                        frame = frames[i]
                        frame.tileId = frameVariantMap.get("tileid", 0)
                        frame.duration = frameVariantMap.get("duration", 0)
                    
                    tile.setFrames(frames)

        
        # Read tile properties
        propertiesVariantMap = variantMap.get("tileproperties", {})
        
        for it in propertiesVariantMap.items():
            tileIndex = Int(it[0])
            propertiesVar = it[1]
            if (tileIndex >= 0 and tileIndex < tileset.tileCount()):
                properties = self.toProperties(propertiesVar)
                tileset.tileAt(tileIndex).setProperties(properties)

        if not self.mReadingExternalTileset:        
            self.mGidMapper.insert(firstGid, tileset)

        return tileset

    def toLayer(self, variant):
        variantMap = variant
        layer = None
        if (variantMap["type"] == "tilelayer"):
            layer = self.toTileLayer(variantMap)
        elif (variantMap["type"] == "objectgroup"):
            layer = self.toObjectGroup(variantMap)
        elif (variantMap["type"] == "imagelayer"):
            layer = self.toImageLayer(variantMap)
        if (layer):
            layer.setProperties(self.toProperties(variantMap.get("properties", {})))
            offset = QPointF(variantMap.get("offsetx", 0.0),  variantMap.get("offsety", 0.0))
            layer.setOffset(offset)

        return layer
    
    def toTileLayer(self, variantMap):
        name = variantMap.get("name",'')
        width = variantMap.get("width",0)
        height = variantMap.get("height",0)
        dataVariant = variantMap["data"]
        
        tileLayer = TileLayer(name,
                             variantMap.get("x",0),
                             variantMap.get("y",0),
                             width, height)
        opacity = variantMap.get("opacity", 0.0)
        visible = variantMap.get("visible", True)
        tileLayer.setOpacity(opacity)
        tileLayer.setVisible(visible)

        encoding = variantMap.get("encoding", '')
        compression = variantMap.get("compression", '')

        if encoding=='' or encoding == "csv":
            layerDataFormat = Map.LayerDataFormat.CSV
        elif (encoding == "base64"):
            if compression=='':
                layerDataFormat = Map.LayerDataFormat.Base64
            elif (compression == "gzip"):
                layerDataFormat = Map.LayerDataFormat.Base64Gzip
            elif (compression == "zlib"):
                layerDataFormat = Map.LayerDataFormat.Base64Zlib
            else:
                self.mError = self.tr("Compression method '%s' not supported"%compression)
                return None
        else:
            self.mError = self.tr("Unknown encoding: %1"%encoding)
            return None

        self.mMap.setLayerDataFormat(layerDataFormat)

        if layerDataFormat==Map.LayerDataFormat.XML or layerDataFormat==Map.LayerDataFormat.CSV:
            dataVariantList = dataVariant

            if len(dataVariantList) != width * height:
                self.mError = self.tr("Corrupt layer data for layer '%s'"%name)
                return None
            x = 0
            y = 0
            ok = False
            
            if (len(dataVariantList) != width * height):
                self.mError = self.tr("Corrupt layer data for layer '%s'"%name)
                return None
            for gidVariant in dataVariantList:
                gid, ok = Int2(gidVariant)
                if (not ok):
                    self.mError = self.tr("Unable to parse tile at (%d,%d) on layer '%s'"%(x, y, tileLayer.name()))
                    return None
                
                cell = self.mGidMapper.gidToCell(gid, ok)
                tileLayer.setCell(x, y, cell)
                x += 1
                if (x >= tileLayer.width()):
                    x = 0
                    y += 1
        elif layerDataFormat==Map.LayerDataFormat.Base64 \
            or layerDataFormat==Map.LayerDataFormat.Base64Zlib \
            or layerDataFormat==Map.LayerDataFormat.Base64Gzip:
            data = QByteArray(dataVariant)
            error = self.mGidMapper.decodeLayerData(tileLayer, data, layerDataFormat)

            if error==DecodeError.CorruptLayerData:
                self.mError = self.tr("Corrupt layer data for layer '%s'"%name)
                return None
            elif error==DecodeError.TileButNoTilesets:
                self.mError = self.tr("Tile used but no tilesets specified")
                return None
            elif error==DecodeError.InvalidTile:
                self.mError = self.tr("Invalid tile: %d"%self.mGidMapper.invalidTile())
                return None
            elif error==DecodeError.NoError:
                pass
            
        return tileLayer

    def toObjectGroup(self, variantMap):
        objectGroup = ObjectGroup(variantMap.get("name",''),
                               variantMap.get("x",0),
                               variantMap.get("y",0),
                               variantMap.get("width",0),
                               variantMap.get("height",0))
        opacity = variantMap.get("opacity", 0.0)
        visible = variantMap.get("visible", True)
        objectGroup.setOpacity(opacity)
        objectGroup.setVisible(visible)
        objectGroup.setColor(variantMap.get("color", ''))
        drawOrderString = variantMap.get("draworder", '')
        if drawOrderString != '':
            objectGroup.setDrawOrder(drawOrderFromString(drawOrderString))
            if (objectGroup.drawOrder() == ObjectGroup.DrawOrder.UnknownOrder):
                self.mError = self.tr("Invalid draw order: %s"%drawOrderString)
                return None

        for objectVariant in variantMap.get("objects", []):
            objectVariantMap = objectVariant
            name = objectVariantMap.get("name",'')
            type = objectVariantMap.get("type", '')
            id = objectVariantMap.get("id",0.0)
            gid = objectVariantMap.get("gid",0.0)
            x = objectVariantMap.get("x",0.0)
            y = objectVariantMap.get("y",0.0)
            width = objectVariantMap.get("width",0.0)
            height = objectVariantMap.get("height",0.0)
            rotation = objectVariantMap.get("rotation", 0.0)
            pos = QPointF(x, y)
            size = QSizeF(width, height)
            object = MapObject(name, type, pos, size)
            object.setId(id)
            object.setRotation(rotation)
            if (gid):
                cell, ok = self.mGidMapper.gidToCell(gid)
                object.setCell(cell)
                if not object.cell().isEmpty():
                    tileSize = object.cell().tile.size()
                    if width == 0:
                        object.setWidth(tileSize.width())
                    if height == 0:
                        object.setHeight(tileSize.height())
                        
            if (objectVariantMap.__contains__("visible")):
                object.setVisible(objectVariantMap.get("visible", True))
            object.setProperties(self.toProperties(objectVariantMap.get("properties", {})))
            objectGroup.addObject(object)
            polylineVariant = objectVariantMap.get("polyline", [])
            polygonVariant = objectVariantMap.get("polygon", [])
            if len(polygonVariant) > 0:
                object.setShape(MapObject.Polygon)
                object.setPolygon(self.toPolygon(polygonVariant))
            
            if len(polylineVariant) > 0:
                object.setShape(MapObject.Polyline)
                object.setPolygon(self.toPolygon(polylineVariant))
            
            if (objectVariantMap.__contains__("ellipse")):
                object.setShape(MapObject.Ellipse)
        
        return objectGroup
        
    def toImageLayer(self, variantMap):
        imageLayer = ImageLayer(variantMap.get("name",''),
                                                variantMap.get("x",0),
                                                variantMap.get("y",0),
                                                variantMap.get("width",0),
                                                variantMap.get("height",0))
        opacity = variantMap.get("opacity", 0.0)
        visible = variantMap.get("visible", True)
        imageLayer.setOpacity(opacity)
        imageLayer.setVisible(visible)
        trans = variantMap.get("transparentcolor", '')
        if (not trans.isEmpty() and QColor.isValidColor(trans)):
            imageLayer.setTransparentColor(QColor(trans))
        imageVariant = variantMap.get("image",'')
        if imageVariant != '':
            imagePath = resolvePath(self.mMapDir, imageVariant)
            if (not imageLayer.loadFromImage(QImage(imagePath), imagePath)):
                self.mError = self.tr("Error loading image:\n'%s'"%imagePath)
                return None

        return imageLayer
        
    def toPolygon(self, variant):
        polygon = QPolygonF()
        for pointVariant in variant:
            pointVariantMap = pointVariant
            pointX = pointVariantMap.get("x",0.0)
            pointY = pointVariantMap.get("y",0.0)
            polygon.append(QPointF(pointX, pointY))
        
        return polygon