<commit_before>import Fertigkeiten
import lxml.etree as etree
from Hilfsmethoden import Hilfsmethoden, VoraussetzungException
import os.path
import Objekte
from PyQt5 import QtWidgets
from Wolke import Wolke
import logging

class DatabaseException(Exception):
    pass

class Datenbank():
    def __init__(self):
        self.vorteile = {}
        self.fertigkeiten = {}
        self.talente = {}
        self.übernatürlicheFertigkeiten = {}
        self.waffen = {}
        self.manöver = {}
        self.removeList = []
        
        self.datei = None
        if Wolke.Settings['Datenbank']:
            tmp = os.path.join(Wolke.Settings['Pfad-Regeln'], 
                               Wolke.Settings['Datenbank'])
            if os.path.isfile(tmp):
                self.datei = tmp
        self.userDbXml = None
        self.loaded = False
        self.xmlLaden()              

    def xmlSchreiben(self):
        Wolke.Fehlercode = -26
        root = etree.Element('Datenbank')
        
        #Vorteile
        Wolke.Fehlercode = -27
        for vort in self.vorteile:
            vorteil = self.vorteile[vort]
            if not vorteil.isUserAdded: continue
            v = etree.SubElement(root,'Vorteil')
            v.set('name',vorteil.name)
            v.set('kosten',str(vorteil.kosten))
            v.set('voraussetzungen',Hilfsmethoden.VorArray2Str(vorteil.voraussetzungen, None))
            v.set('nachkauf',vorteil.nachkauf)
            v.set('typ', str(vorteil.typ))
            v.set('variable', str(vorteil.variable))
            v.text = vorteil.text

        #Talente
        Wolke.Fehlercode = -28
        for tal in self.talente:
            talent = self.talente[tal]
            if not talent.isUserAdded: continue
            v = etree.SubElement(root,'Talent')
            v.set('name',talent.name)
            v.set('kosten',str(talent.kosten))
            v.set('voraussetzungen',Hilfsmethoden.VorArray2Str(talent.voraussetzungen, None))
            v.set('verbilligt',str(talent.verbilligt))
            v.set('fertigkeiten',Hilfsmethoden.FertArray2Str(talent.fertigkeiten, None))
            v.set('variable',str(talent.variable))
            v.set('printclass',str(talent.printclass))
            v.text = talent.text
            
        #Fertigkeiten
        Wolke.Fehlercode = -29
        for fer in self.fertigkeiten:
            fertigkeit = self.fertigkeiten[fer]
            if not fertigkeit.isUserAdded: continue
            v = etree.SubElement(root,'Fertigkeit')
            v.set('name',fertigkeit.name)
            v.set('steigerungsfaktor',str(fertigkeit.steigerungsfaktor))
            v.set('voraussetzungen',Hilfsmethoden.VorArray2Str(fertigkeit.voraussetzungen, None))
            v.set('attribute',Hilfsmethoden.AttrArray2Str(fertigkeit.attribute))
            v.set('kampffertigkeit',str(fertigkeit.kampffertigkeit))
            v.text = fertigkeit.text

        Wolke.Fehlercode = -30
        for fer in self.übernatürlicheFertigkeiten:
            fertigkeit = self.übernatürlicheFertigkeiten[fer]
            if not fertigkeit.isUserAdded: continue
            v = etree.SubElement(root,'Übernatürliche-Fertigkeit')
            v.set('name',fertigkeit.name)
            v.set('steigerungsfaktor',str(fertigkeit.steigerungsfaktor))
            v.set('voraussetzungen',Hilfsmethoden.VorArray2Str(fertigkeit.voraussetzungen, None))
            v.set('attribute',Hilfsmethoden.AttrArray2Str(fertigkeit.attribute))
            v.text = fertigkeit.text
                                                    
        #Waffen
        Wolke.Fehlercode = -31
        for wa in self.waffen:
            waffe = self.waffen[wa]
            if not waffe.isUserAdded: continue
            w = etree.SubElement(root,'Waffe')
            w.set('name', waffe.name)
            w.set('W6', str(waffe.W6))
            w.set('plus', str(waffe.plus))
            w.set('haerte', str(waffe.haerte))
            w.text = ", ".join(waffe.eigenschaften)
            w.set('fertigkeit', waffe.fertigkeit)
            w.set('talent', waffe.talent)
            w.set('beid', str(waffe.beid))
            w.set('pari', str(waffe.pari))
            w.set('reit', str(waffe.reit))
            w.set('schi', str(waffe.schi))
            w.set('kraf', str(waffe.kraf))
            w.set('schn', str(waffe.schn))
            w.set('rw', str(waffe.rw))
            if type(waffe) == Objekte.Fernkampfwaffe:
                w.set('lz', str(waffe.lz))
                w.set('fk', '1')
            else:
                w.set('wm', str(waffe.wm))
                w.set('fk', '0')

        #Manöver
        Wolke.Fehlercode = -34
        for ma in self.manöver:
            manöver = self.manöver[ma]
            if not manöver.isUserAdded: continue
            m = etree.SubElement(root, 'Manoever')
            m.set('name', manöver.name)
            m.set('typ', str(manöver.typ))
            m.set('voraussetzungen', Hilfsmethoden.VorArray2Str(manöver.voraussetzungen, None))
            m.set('probe', manöver.probe)
            m.set('gegenprobe', manöver.gegenprobe)
            m.text = manöver.text

        #Remove list
        for rm in self.removeList:
            r = etree.SubElement(root,'Remove')
            r.set('name', rm[0])
            r.set('typ', rm[1])

        #Write XML to file
        Wolke.Fehlercode = -26
        doc = etree.ElementTree(root)
        Wolke.Fehlercode = -32
        with open(self.datei,'wb') as file:
            file.seek(0)
            file.truncate()
            doc.write(file, encoding='UTF-8', pretty_print=True)
            file.truncate()
            
        Wolke.Fehlercode = 0

    def xmlLaden(self):
        self.vorteile = {}
        self.fertigkeiten = {}
        self.talente = {}
        self.übernatürlicheFertigkeiten = {}
        self.waffen = {}
        self.manöver = {}
        self.removeList = []

        if os.path.isfile('datenbank.xml'):
            self.xmlLadenInternal('datenbank.xml', refDB=True)
            self.loaded = True

        try:   
            if self.datei and os.path.isfile(self.datei):
                self.xmlLadenInternal(self.datei, refDB=False) 
        except DatabaseException:
            messagebox = QtWidgets.QMessageBox()
            messagebox.setWindowTitle("Fehler!")
            messagebox.setText(self.datei + " ist keine valide Datenbank-Datei!")
            messagebox.setIcon(QtWidgets.QMessageBox.Critical)
            messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
            messagebox.exec_()

    def xmlLadenInternal(self, file, refDB):
        Wolke.Fehlercode = -20
        root = etree.parse(file).getroot()

        if not refDB:
            self.userDbXml = root

        if root.tag != 'Datenbank':
            raise DatabaseException('Not a valid database file')

        numLoaded = 0
        <commit_msg>Add schema migration support for user db<commit_after>import Fertigkeiten
import lxml.etree as etree
from Hilfsmethoden import Hilfsmethoden, VoraussetzungException
import os.path
import Objekte
from PyQt5 import QtWidgets
from Wolke import Wolke
import logging

class DatabaseException(Exception):
    pass

class Datenbank():
    def __init__(self):
        self.vorteile = {}
        self.fertigkeiten = {}
        self.talente = {}
        self.übernatürlicheFertigkeiten = {}
        self.waffen = {}
        self.manöver = {}
        self.removeList = []
        
        self.datei = None
        if Wolke.Settings['Datenbank']:
            tmp = os.path.join(Wolke.Settings['Pfad-Regeln'], 
                               Wolke.Settings['Datenbank'])
            if os.path.isfile(tmp):
                self.datei = tmp
        self.userDbXml = None
        self.loaded = False

        #Versionierung
        #Wenn sich das Schema der Ref-DB ändert müssen bestehende user dbs aktualisiert werden.
        #Hier kann die Datenbank Code Version inkrementiert werden und in der Migrationen-Map eine Migrationsfunktion für die neue Version angelegt werden.
        #In dieser Funktion kann dann die UserDB-XML-Datei angepasst werden, bevor sie geladen wird.
        #Da Migrationen hier im Gegensatz zur Charaktermigration nur bei Schema-Änderungen nötig sind, gibt es nichts was wir dem User in einer messagebox zeigen müssten
        #Die Funktionen werden inkrementell ausgeführt, bspw. bei UserDB-Version '0' und DB-Code-Version '2' wird zuerst die Funktion für 1, dann die Funktion für 2 aufgerufen
        self.datenbankCodeVersion = 0
        self.migrationen = [
            lambda xmlRoot: None, #nichts zu tun, initiale db version   
        ]
        if not self.migrationen[self.datenbankCodeVersion]:
            raise Exception("Migrations-Code vergessen.")

        self.xmlLaden()              

    def xmlSchreiben(self):
        Wolke.Fehlercode = -26
        root = etree.Element('Datenbank')
        
        #Vorteile
        Wolke.Fehlercode = -27
        for vort in self.vorteile:
            vorteil = self.vorteile[vort]
            if not vorteil.isUserAdded: continue
            v = etree.SubElement(root,'Vorteil')
            v.set('name',vorteil.name)
            v.set('kosten',str(vorteil.kosten))
            v.set('voraussetzungen',Hilfsmethoden.VorArray2Str(vorteil.voraussetzungen, None))
            v.set('nachkauf',vorteil.nachkauf)
            v.set('typ', str(vorteil.typ))
            v.set('variable', str(vorteil.variable))
            v.text = vorteil.text

        #Talente
        Wolke.Fehlercode = -28
        for tal in self.talente:
            talent = self.talente[tal]
            if not talent.isUserAdded: continue
            v = etree.SubElement(root,'Talent')
            v.set('name',talent.name)
            v.set('kosten',str(talent.kosten))
            v.set('voraussetzungen',Hilfsmethoden.VorArray2Str(talent.voraussetzungen, None))
            v.set('verbilligt',str(talent.verbilligt))
            v.set('fertigkeiten',Hilfsmethoden.FertArray2Str(talent.fertigkeiten, None))
            v.set('variable',str(talent.variable))
            v.set('printclass',str(talent.printclass))
            v.text = talent.text
            
        #Fertigkeiten
        Wolke.Fehlercode = -29
        for fer in self.fertigkeiten:
            fertigkeit = self.fertigkeiten[fer]
            if not fertigkeit.isUserAdded: continue
            v = etree.SubElement(root,'Fertigkeit')
            v.set('name',fertigkeit.name)
            v.set('steigerungsfaktor',str(fertigkeit.steigerungsfaktor))
            v.set('voraussetzungen',Hilfsmethoden.VorArray2Str(fertigkeit.voraussetzungen, None))
            v.set('attribute',Hilfsmethoden.AttrArray2Str(fertigkeit.attribute))
            v.set('kampffertigkeit',str(fertigkeit.kampffertigkeit))
            v.text = fertigkeit.text

        Wolke.Fehlercode = -30
        for fer in self.übernatürlicheFertigkeiten:
            fertigkeit = self.übernatürlicheFertigkeiten[fer]
            if not fertigkeit.isUserAdded: continue
            v = etree.SubElement(root,'Übernatürliche-Fertigkeit')
            v.set('name',fertigkeit.name)
            v.set('steigerungsfaktor',str(fertigkeit.steigerungsfaktor))
            v.set('voraussetzungen',Hilfsmethoden.VorArray2Str(fertigkeit.voraussetzungen, None))
            v.set('attribute',Hilfsmethoden.AttrArray2Str(fertigkeit.attribute))
            v.text = fertigkeit.text
                                                    
        #Waffen
        Wolke.Fehlercode = -31
        for wa in self.waffen:
            waffe = self.waffen[wa]
            if not waffe.isUserAdded: continue
            w = etree.SubElement(root,'Waffe')
            w.set('name', waffe.name)
            w.set('W6', str(waffe.W6))
            w.set('plus', str(waffe.plus))
            w.set('haerte', str(waffe.haerte))
            w.text = ", ".join(waffe.eigenschaften)
            w.set('fertigkeit', waffe.fertigkeit)
            w.set('talent', waffe.talent)
            w.set('beid', str(waffe.beid))
            w.set('pari', str(waffe.pari))
            w.set('reit', str(waffe.reit))
            w.set('schi', str(waffe.schi))
            w.set('kraf', str(waffe.kraf))
            w.set('schn', str(waffe.schn))
            w.set('rw', str(waffe.rw))
            if type(waffe) == Objekte.Fernkampfwaffe:
                w.set('lz', str(waffe.lz))
                w.set('fk', '1')
            else:
                w.set('wm', str(waffe.wm))
                w.set('fk', '0')

        #Manöver
        Wolke.Fehlercode = -34
        for ma in self.manöver:
            manöver = self.manöver[ma]
            if not manöver.isUserAdded: continue
            m = etree.SubElement(root, 'Manoever')
            m.set('name', manöver.name)
            m.set('typ', str(manöver.typ))
            m.set('voraussetzungen', Hilfsmethoden.VorArray2Str(manöver.voraussetzungen, None))
            m.set('probe', manöver.probe)
            m.set('gegenprobe', manöver.gegenprobe)
            m.text = manöver.text

        #Remove list
        for rm in self.removeList:
            r = etree.SubElement(root,'Remove')
            r.set('name', rm[0])
            r.set('typ', rm[1])

        #Write XML to file
        Wolke.Fehlercode = -26
        doc = etree.ElementTree(root)
        Wolke.Fehlercode = -32
        with open(self.datei,'wb') as file:
            file.seek(0)
            file.truncate()
            doc.write(file, encoding='UTF-8', pretty_print=True)
            file.truncate()
            
        Wolke.Fehlercode = 0

    def xmlLaden(self):
        self.vorteile = {}
        self.fertigkeiten = {}
        self.talente = {}
        self.übernatürlicheFertigkeiten = {}
        self.waffen = {}
        self.manöver = {}
        self.removeList = []

        if os.path.isfile('datenbank.xml'):
            self.xmlLadenInternal('datenbank.xml', refDB=True)
            self.loaded = True

        try:   
            if self.datei and os.path.isfile(self.datei):
                self.xmlLadenInternal(self.datei, refDB=False) 
        except DatabaseException:
            messagebox = QtWidgets.QMessageBox()
            messagebox.setWindowTitle("Fehler!")
            messagebox.setText(self.datei + " ist keine valide Datenbank-Datei!")
            messagebox.setIcon(QtWidgets.QMessageBox.Critical)
            messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
            messagebox.exec_()

    def userDBMigrieren(self, xmlRoot, userDBVersion, datenbankCodeVersion):
        dbChanged = userDBVersion < datenbankCodeVersion
        while userDBVersion < datenbankCodeVersion:
            logging.warning("Migriere UserDB von Version " + str(userDBVersion ) + " zu " + str(userDBVersion + 1))
            userDBVersion +=1
            self.migrationen[userDBVersion](xmlRoot)

    def xmlLadenInternal(self, file, refDB):
        Wolke.Fehlercode = -20
        root = etree.parse(file).getroot()

        if root.tag != 'Datenbank':
            raise DatabaseException('Not a valid database file')

        if not refDB:
            self.userDbXml = root

            #Versionierung
            versionXml = root.find('Version')
            userDBVersion = 0
            if versionXml is not None:
                logging.debug("User DB: VersionXML found")
                userDBVersion = int(versionXml.find('DatenbankVersion').text)

            logging.debug("Starting User DB Migration")
            self.userDBMigrieren(root, userDBVersion, self.datenbankCodeVersion)

        numLoaded = 0
        
{'fraction_non_alphanumeric': 0.11206065668572897, 'fraction_numerical': 0.005525926877851314, 'mean_word_length': 1.9668318719024018, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 21}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7503263', 'n_tokens_mistral': 5015, 'n_tokens_neox': 4966, 'n_words': 832}