# -*- coding: utf-8 -*-
"""
/***************************************************************************
 walidatorPlikowGML
                                 A QGIS plugin
 Walidacja i kontrola plików GML baz: BDOO, BDOT10k, PRNG, RCN, GESUT, EGiB, BDOT500, MGR, KARTO10k
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2022-12-23
        git sha              : $Format:%H$
        copyright            : (C) 2026 by Główny Urząd Geodezji i Kartografii (GUGiK) - Marcin Lebiecki
        email                : marcin.lebiecki@gugik.gov.pl
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from qgis.core import *
from qgis.PyQt.QtCore import *
from qgis import processing
from qgis.PyQt.QtWidgets import *
import pathlib
import copy
import re
import time
import sys
from datetime import datetime
from collections import defaultdict
import lxml
from lxml import etree
import zipfile
import os, subprocess
import itertools
import sip
from .utils import *
from qgis.core import QgsMessageLog, QgsProject, QgsSpatialIndex
from collections import defaultdict, Counter
from lxml.etree import parse, XMLSchema, ElementTree
from time import sleep
from datetime import datetime
from osgeo import ogr,osr, gdal
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import QFileDialog, QProgressBar, QMessageBox, QAction, QLabel

class WalidatorEGIB:
    
    def __init__(self):
        # opcjonalnie: pólka na cache’y (niekonieczne)
        pass
    
    def kontrolaAtrybutow_EGIB(self):
        # wartosci globalne przepisane z głownego pliku
        """Ta wersja raz na starcie wiąże warstwy błędów, a potem
          pracuje na gotowych obiektach – mniej „migania” warstw i mniejsze ryzyko duplikatów.
          dzięki czemu QGIS zużywa mniej pamięci i nie dubluje warstw błędów. """
        # upewnij się, że model istnieje
        modelKontroli = self.dlg.treeView.model()
        if modelKontroli is None:
            # awaryjnie utwórz – ale to będzie rzadkie, bo główny już go robi
            modelKontroli = QStandardItemModel()
            modelKontroli.setHorizontalHeaderLabels(['Lista kontroli'])
            self.dlg.treeView.setModel(modelKontroli)
            self.dlg.treeView.setUniformRowHeights(True)
        # (opcjonalnie) zapamiętaj referencję na sobie – wygodne do debugów
        self.modelKontroli = modelKontroli
        if not hasattr(self, 'err_layers'):
            self.przygotujWarstwyBledow()
        #2) Sparsowane pliki – spróbuj kolejno: atrybut instancji → global → []
        plikiZparsowane_egib = getattr(self, 'plikiZparsowane', None) or globals().get('plikiZparsowane', [])
        if plikiZparsowane_egib is None:
            plikiZparsowane_egib = globals().get('plikiZparsowane', [])
            # teraz używaj plikiZparsowane_egib
        # 3) Bieżąca grupa w projekcie – atrybut → global → None
        groupaGlowna_egib = getattr(self, 'groupaGlowna', None)
        if groupaGlowna_egib is None:
           groupaGlowna_egib = globals().get('groupaGlowna', None)
        # teraz używaj groupaGlowna_egib
        for i in range(modelKontroli.rowCount()):
            parent = modelKontroli.item(i)
            if parent.checkState() in (1,2):
                for j in range(parent.rowCount()):
                    kontrola = self.pobierzDaneKontroli(parent, j)
                    if not kontrola:
                        continue
                    for k in self.przygotujDaneWarstwy(kontrola, plikiZparsowane_egib, groupaGlowna_egib, parent, j):
                        layers_in_group = k.get('layers_in_group') or []
                        if not layers_in_group:
                            continue
                        # 2) wyznacz „token” z XML lub z nazwy warstwy 
                        klasa = k.get('klasa') or ''
                        expected = klasa.upper()
                        def _egib_token(name: str) -> str:
                            """
                            Z nazwy warstwy QGIS EGiB (np. 'EGiB_0861011_EGB_Budynek')
                            wyciąga 'EGB_BUDYNEK' – czyli to, co przychodzi w <Kontrola class="...">.
                            """
                            nm = name.upper()
                            pos = nm.rfind("EGB_")
                            if pos >= 0:
                                return nm[pos:]
                            return nm  # awaryjnie: cała nazwa
                        

                        tokens_map = {lyr: _egib_token(lyr.name()) for lyr in layers_in_group}
                        target_layers = [
                            lyr for lyr, tok in tokens_map.items()
                            if tok == expected or tok.startswith(expected)
                            ]

                        if not target_layers:
                            continue  # żadnej warstwy – pomijamy tę kontrolę
    
                        # 4) URUCHOM kontrolę OSOBNO dla każdej warstwy
                        for lyr in target_layers:
                            sl = dict(k)
                            sl['layerObj'] = lyr
                            self.wykonajJednaKontrole(sl)
                            
    def przygotujWarstwyBledow(self):
        """ Tworzymy 3 warstwy pamięciowe: Point / LineString / Polygon 
        Działają przez cały bieg programu 
        Dzięki temu unikamy duplikatów i spadków wydajności."""
        crs = None
        crs = crs or "EPSG:2180"
        self.err_layers = {
           'Point'     : self.sprawdzCzyWarstwaIstnieje('WarstwaBledow_Point', 'Point', crs),
           'LineString': self.sprawdzCzyWarstwaIstnieje('WarstwaBledow_LineString', 'LineString', crs),
           'Polygon'   : self.sprawdzCzyWarstwaIstnieje('WarstwaBledow_Polygon', 'Polygon', crs),
           }
       
        
    def klasaDoRaportowania(self, parsedFileName: str, klasa: str) -> str:
         return klasa or ''
                    
                    
    def zywaWarstwa(self, lyr, *, debug=False):
        if not lyr:
            return False
        try:
            return (not sip.isdeleted(lyr)) and (lyr.id() in QgsProject.instance().mapLayers())
        except Exception:
            return False
    
    def pobierzDaneKontroli(self, parent, j):
        """Tutaj czyścimy zapisane w XML-u znaki "&gt;"/"&lt;" → ">" / "<".
         Dzięki temu wyrażenia (QgsExpression) działają poprawnie"""
        child = parent.child(j)
        # Sprawdzenie, czy wiersz jest zaznaczony i kompletny
        if child.checkState() != 2:
            return None
        if any(child.data(i) == '' for i in range(1, 7)):
            return None
        # Odczyt danych
        raw = child.data(6)
        typ = (child.data(5) or "").strip()
        if typ not in ('QgsExpression','QgsExpressionWithJoin','pythonFunction','PyExpression','MgrExpression'):
            return None
        kontrola = {
            'idKontroli': child.data(1),
            'nazwaKontroli':  parent.data(2),
            'klasa': child.data(3),
            'errorPhrase': child.data(4),
            'typ': typ,
            'sqltxt': (raw or "").replace("&gt;", ">").replace("&lt;", "<")
            }
        return kontrola
    
    
    
    def przygotujDaneWarstwy(self,  kontrola, plikiZparsowane_egib, groupaGlowna_egib, parent, j):
        """Szuka warstwy po nazwie klasy i grupie, bez wielokrotnych wyszukiwań w projekcie.
         Dodatkowo dopasowujemy plik GML do warstwy po nazwie pliku niezależnie od liczby znaków.
         dzięki temu jest szybko i stabilnie, oraz  mniej odwołań do katalogu warstw."""
        klasa = kontrola['klasa']
        sqltxt = kontrola['sqltxt']
        errorPhrase = kontrola['errorPhrase']
        nazwaKontroli = kontrola['nazwaKontroli']
        typ = kontrola['typ']
        if not groupaGlowna_egib:
            print("DBG EGiB: groupaGlowna_egib == None")
            return
        layers_in_group = [node.layer() for node in groupaGlowna_egib.children() if isinstance(node, QgsLayerTreeLayer)]
        if not layers_in_group:
            print("DBG EGiB: pusty groupaGlowna_egib (brak warstw)")
            return

        # 3) Dopasuj „zparsowany plik” po nazwie 
        parsedFileName = None
        zparsowanyPlik = None
        
        if plikiZparsowane_egib:
            base_group = groupaGlowna_egib.name() if groupaGlowna_egib else ""
            for p in plikiZparsowane_egib:
                base = os.path.basename(p)
                if base_group and base.startswith(base_group):
                    zparsowanyPlik = p
                    parsedFileName = base
                    break
             # fallback: pierwszy z listy, jeśli powyższe się nie udało
            if zparsowanyPlik is None:
                 zparsowanyPlik = plikiZparsowane_egib[0]
                 parsedFileName = os.path.basename(zparsowanyPlik)
      
        slownikFinalny = {
            'idKontroli'   : kontrola['idKontroli'],
            'zparsowanyPlik': zparsowanyPlik,
            'parsedFileName': parsedFileName,
            'klasa': klasa,
            'zapytanie': sqltxt,
            'rodzic': parent,
            'wiersz': j,
            'errorPhrase': errorPhrase,
            'nazwaKontroli': nazwaKontroli,
            'layerObj': None,
            'typ': typ,
            'layers_in_group': layers_in_group
            }
        yield slownikFinalny
        
        
        
    def sprawdzCzyWarstwaIstnieje(self, name: str, geom: str, crs_authid: str = "EPSG:2180"):
        # sprawdzenie istnienia warstwęy; jeśli nie – tworzy ją i zwraca referencję
        proj = QgsProject.instance()
        existing = proj.mapLayersByName(name)
        if existing:
            return existing[0]
        # utworzenie  warstwy z polami: gml_id, klasa, komunikat i dodanie w przypakdu jej braku
        wkb = {'Point': QgsWkbTypes.Point, 'LineString': QgsWkbTypes.LineString, 'Polygon': QgsWkbTypes.Polygon}[geom]
        vlayer = QgsVectorLayer(QgsWkbTypes.displayString(wkb) + f"?crs={crs_authid}", name, "memory")
        pr = vlayer.dataProvider()
        pr.addAttributes([
            QgsField('gml_id', QVariant.String),
            QgsField('klasa', QVariant.String),
            QgsField('komunikat', QVariant.String),
            ])
        vlayer.updateFields()
        proj.addMapLayer(vlayer)
        # Dzięki temu raport zawsze dostaje komplet atrybutów, niezależnie od historii warstwy.
        return vlayer
    
    
    
    def bezpiecznePrzywolaniePlikuUtilsPy(self, func, *args):
        """ Bezpieczne wołanie funkcji pomocniczych
        Nie przerywamy całej kontroli w przypadkach błędnych
        tylko zwracamy pusty wynik i logujemy przyczynę.
        Dzięki temu pojedyncza pomyłka w danych nie zatrzymuje całego procesu.
        Woła funkcję z utils.py w sposób bezpieczny.
        Zwraca [] zamiast wyjątku"""
        try:
            return func(*args)
        except StopIteration:
            QgsMessageLog.logMessage("[walidator] utils: StopIteration → traktuję jako brak wyników",
                                 "Walidator", Qgis.Warning)
            return []
        except QgsProcessingException as e:
            QgsMessageLog.logMessage(f"[walidator] utils: QgsProcessingException: {e}",
                                 "Walidator", Qgis.Critical)
            return []
        except Exception as e:
            QgsMessageLog.logMessage(f"[walidator] utils: {type(e).__name__}: {e}",
                                 "Walidator", Qgis.Warning)
            return []
        
    # funkcja pomocnicza do parsowania (unikamy duplikacji)
    def sprawdzParsowanie(self, p):
        try:
            return etree.parse(p)
        except Exception:
            return None
        
    
    
    def wykonajJednaKontrole(self,slownikFinalny):
              # funkcja wykonuję jedną kontrolę
              global  slownikBledow, warstwyBledowKontroliAtrybutow
              global kontrolowanePliki_df, klasy_df, gmlid_df_k, komunikatyBledowKontroli_df
              slownikBledow = getattr(self, 'slownikBledow', None)
              komunikatyBledowKontroli_df = getattr(self, 'komunikatyBledowKontroli_df', None)
              kontrolowanePliki_df = getattr(self, 'kontrolowanePliki_df', None)
              klasy_df = getattr(self, 'klasy_df', None)
              gmlid_df_w = getattr(self, 'gmlid_df_w', None)
              gmlid_df_k = getattr(self, 'gmlid_df_k', None)
              grupyKontroli_df = getattr(self, 'grupyKontroli_df', None)
              liczbaKontroliWykonanych = getattr(self, 'liczbaKontroliWykonanych', None)
              idkontroli_LiczbaBledow = getattr(self, 'idkontroli_LiczbaBledow', None)
              progress = getattr(self, 'progress', None)
              if not hasattr(self, 'err_layers'):
                  self.przygotujWarstwyBledow() # przygotowuje dane do kontroli
              BATCH = 500
              sqltxt = slownikFinalny.get('zapytanie') or ''
              rodzic = slownikFinalny.get('rodzic')
              parsedFileName = slownikFinalny.get('parsedFileName') or ''
              klasa = slownikFinalny.get('klasa')
              wiersz = slownikFinalny.get('wiersz')
              kod = rodzic.child(wiersz).data(1) if rodzic and wiersz is not None else '—'
              zparsowanyPlik = slownikFinalny.get('zparsowanyPlik') or (self.plikiZparsowane[0] if self.plikiZparsowane else None)
              idKontroli = slownikFinalny.get('idKontroli')
              errorPhrase = slownikFinalny.get('errorPhrase')
              nazwaKontroli = slownikFinalny.get('nazwaKontroli')
              layer = slownikFinalny.get('layerObj')
              typKontroli = slownikFinalny.get('typ')
              layerTMP = slownikFinalny.get('layerObj') or layer 
              if isinstance(layerTMP, (list, tuple)):
                  layerTMP = layerTMP[0] if layerTMP else None
              if not layerTMP or not self.zywaWarstwa(layerTMP):
                  return iter(())
              layers_in_group = slownikFinalny.get('layers_in_group') or ([layerTMP] if layerTMP else [])
              # raz na wejściu gałęzi EGiB: sparsuj plik (jeśli jeszcze nie)
              doc_once = None
              if zparsowanyPlik:
                  doc_once = self.sprawdzParsowanie(zparsowanyPlik)
                  if doc_once is None:
                      print("DBG: nie udało się sparsować GML/XML (EGiB) → pomijam kontrolę")
              
              try:
                 
                  start_time = datetime.now()
                  # domyślnie pusty iterator
                  requestFeatures = None
                  """ 1) QgsExpression: budujemy zapytanie i pobieramy wynik prosto z QGIS (layer.getFeatures).
                  2) Funkcje Python (z utils.py): wołamy je bezpiecznie (bezpiecznePrzywolaniePlikuUtilsPy), 
                  a pliki GML parsujemy tylko raz i przekazujemy jako gotowe drzewo XML (etree.parse).
                  większa odporność na błędy w plikach źródłowych."""
                  if typKontroli in ('QgsExpression','QgsExpressionWithJoin'):
                    if not self.zywaWarstwa(layerTMP):
                        requestFeatures = iter(())
                    else:
                        # 1) weź podstawowy tekst z XML i odkoduj &gt;/&lt;
                        #expr_text = (sqltxt or orig or '')
                        expr_text = sqltxt.replace("&gt;", ">").replace("&lt;", "<")
                        if not expr_text:
                            QgsMessageLog.logMessage("[expr] Puste wyrażenie – pomijam.", "Walidator", Qgis.Warning)
                            requestFeatures = iter(())
                        else:
                            # 2) jesli jest błąd parsera, będzie pusty iterator
                            expr = QgsExpression(expr_text)
                            if expr.hasParserError():
                                requestFeatures = iter(())
                            else:
                                # 3) właściwy iterator 
                                req = QgsFeatureRequest(QgsExpression(expr_text))
                                feats = list(layerTMP.getFeatures(req))  # materializacja
                                requestFeatures = iter(feats)
                               
                  else: 
                      #---test--
                      fname = re.sub(r'\s*\(.*\)\s*$', '', sqltxt).strip()  # bezpiecznie usuń „(gml)”
                    
                      # ---------- (gml) ----------
                      if '(gml)' in sqltxt:
                        fname = sqltxt.replace('(gml)', '').strip()
                        f = globals().get(fname)
                        if f and doc_once is not None and self.zywaWarstwa(layerTMP):
                            rf = self.bezpiecznePrzywolaniePlikuUtilsPy(f, layerTMP, doc_once)
                            if rf is None:
                                requestFeatures = iter(())
                            elif hasattr(rf, '__iter__') and not isinstance(rf, (str, bytes)):
                                requestFeatures = iter(rf)
                            else:
                                requestFeatures = iter([rf])
                        else:
                            requestFeatures = iter(())
    
                        # ---------- (gml,klasa) ----------
                      elif '(gml,klasa)' in sqltxt:
                          fname = sqltxt.replace('(gml,klasa)', '').strip()
                          f = globals().get(fname)
                          klasa_txt = (klasa or '').strip()
                          if f and doc_once is not None and klasa_txt and self.zywaWarstwa(layerTMP):
                              rf = self.bezpiecznePrzywolaniePlikuUtilsPy(f, layerTMP, doc_once, klasa_txt)
                              requestFeatures = iter(rf or [])
                          else:
                              requestFeatures = iter(())


                       # ---------- fallback ----------
                      else: # pozostałe typy
                           fname = sqltxt.strip()
                           f = globals().get(fname)
                           if f and self.zywaWarstwa(layerTMP):
                               rf = self.bezpiecznePrzywolaniePlikuUtilsPy(f, layerTMP)
                               requestFeatures = iter(rf or [])
                           else:
                               requestFeatures = iter(())
                          
                      """[Strumień zamiast listy]
                      requestFeatures to iterator – nie trzymamy całej listy w pamięci.
                      Dzięki temu duże pliki (GML 2–5 GB) nie przeciążą QGIS-a
                      jeden iterator, jedna pętla, jeden kierunek."""
                      if requestFeatures is None:
                          requestFeatures = iter(())   
                  end_time = datetime.now()
                  czas_przetwarzania = end_time - start_time
                  kod = rodzic.child(wiersz).data(1) if rodzic and wiersz is not None else '—'
                  QgsMessageLog.logMessage(f'{kod} - {klasa} - {czas_przetwarzania}', tag="Walidator plików GML", level=Qgis.Info)
                  liczbaBledow = 0
                  buf_point, buf_line, buf_poly = [], [], []
                  liczba_sprawdzonych_wejsc = 0
                  liczba_zapisanych_bledow = 0
                  unikalne_kontrola = set()
                  for feature in requestFeatures:
                      liczba_sprawdzonych_wejsc += 1
                      gmlid = feature.attribute('gml_id') or 'BRAK_ID'
                      if '|' in gmlid:
                              base, suf = gmlid.split('|', 1)
                              gmlid = base
                              errorPhrase_fin = errorPhrase + suf
                      else:
                              errorPhrase_fin = errorPhrase
                      klasaDoRaportowania = self.klasaDoRaportowania(parsedFileName, klasa)
                          # inicjuj listę (stary kod dodawał tylko w else)
                      slownikBledow.setdefault(errorPhrase_fin, []).append((gmlid, self.klasaDoRaportowania(parsedFileName, klasa)))
                      typ_geometrii = QgsWkbTypes.displayString(feature.geometry().wkbType())
                      """przydziel do odpowiedniego bufora wg geometrii
                      podział geometrii - trzy wykluczające się gałęzie na różne rodzaje geometrii
                      dzięki temu nie ma ryzyka, że linie wpadną do jednego z powodu złych wcięć."""
                      if typ_geometrii in ('Point', 'MultiPoint'):
                          errf = self.dodawanieBledow('Point', feature, gmlid, klasaDoRaportowania, errorPhrase_fin)
                          if errf: buf_point.append(errf)
                          if len(buf_point) >= BATCH and self.err_layers['Point']:
                              ok = self.err_layers['Point'].dataProvider().addFeatures(buf_point)
                              if ok:  liczba_zapisanych_bledow += len(buf_point)
                              buf_point.clear()
                      elif typ_geometrii in ('LineString', 'CompoundCurve', 'CircularString', 'MultiLineString'):
                              errf = self.dodawanieBledow('LineString', feature, gmlid, klasaDoRaportowania, errorPhrase_fin)
                              if errf: buf_line.append(errf)
                              if len(buf_line) >= BATCH and self.err_layers['LineString']:
                                  ok = self.err_layers['LineString'].dataProvider().addFeatures(buf_line)
                                  if ok: liczba_zapisanych_bledow += len(buf_line)
                                  buf_line.clear()
                      elif typ_geometrii in ('Polygon', 'MultiPolygon', 'CurvePolygon'):
                                errf = self.dodawanieBledow('Polygon', feature, gmlid, klasaDoRaportowania, errorPhrase_fin)
                                if errf: buf_poly.append(errf)
                                if len(buf_poly) >= BATCH and self.err_layers['Polygon']:
                                      ok = self.err_layers['Polygon'].dataProvider().addFeatures(buf_poly)
                                      if ok: liczba_zapisanych_bledow += len(buf_poly)
                                      buf_poly.clear()
                      # jednorazowy komunikat – zapamiętaj, że już dodaliśmy wpis
                      klucz_wpisu = (parsedFileName, gmlid, errorPhrase_fin, nazwaKontroli)
                      if klucz_wpisu in unikalne_kontrola:
                          # ten sam wiersz już był w raportach – pomijamy „jednorazowy komunikat”
                          continue
                      unikalne_kontrola.add(klucz_wpisu)
                      komunikatyBledowKontroli_df.append(errorPhrase_fin)
                      kontrolowanePliki_df.append(parsedFileName)
                      gmlid_df_k.append(gmlid)
                      klasy_df.append(klasaDoRaportowania)
                      grupyKontroli_df.append(nazwaKontroli)
                      liczbaBledow += 1
                  if buf_point:
                      ok = self.err_layers['Point'].dataProvider().addFeatures(buf_point)
                      if ok:
                          liczba_zapisanych_bledow += len(buf_point)
                  if buf_line:
                      ok = self.err_layers['LineString'].dataProvider().addFeatures(buf_line)
                      if ok:
                          liczba_zapisanych_bledow += len(buf_line)
                  if buf_poly:
                      ok = self.err_layers['Polygon'].dataProvider().addFeatures(buf_poly)
                      if ok:
                          liczba_zapisanych_bledow += len(buf_poly)
                  self.idkontroli_LiczbaBledow[idKontroli] = liczbaBledow
                  if liczbaBledow == 0:
                      self.kontrolaZWynikiemPozytywnym += 1
              except Exception as e:
                   komunikat = rodzic.child(wiersz).data(1) if (rodzic and wiersz is not None) else 'brak_danych'
                   print(f'Błąd przy wykonaniu {komunikat}: {e}')
                   return None
              self.liczbaKontroliWykonanych += 1
              if hasattr(self, "progress"):
                  self.progress.setValue(self.liczbaKontroliWykonanych)
              progress.setValue(self.liczbaKontroliWykonanych)
              
    def dodawanieBledow(self, dest_layer_key, feature, gmlid, klasaDoRaportowania, errorPhrase_fin):
            # tworzymy nowe feature'y
            # w odpowiedniej geometrii i w paczkach (BATCH) co zmniejsza zużycie pamięci
            lyr = self.err_layers.get(dest_layer_key)
            if not lyr:
                return None
            # 1) dopilnuj schematu warstwy błędów
            self.zapewnijAtrybutyBledow(lyr)

            fields = lyr.fields()
            f = QgsFeature(fields)
            f.setGeometry(feature.geometry())
            # 2) jeśli pola są nazwane – ustaw po nazwach,
            idx_gml = fields.indexFromName('gml_id')
            idx_kl  = fields.indexFromName('klasa')
            idx_kom = fields.indexFromName('komunikat')
            if idx_gml != -1 and idx_kl != -1 and idx_kom != -1:
                f.setAttribute(idx_gml, gmlid)
                f.setAttribute(idx_kl , klasaDoRaportowania)
                f.setAttribute(idx_kom, errorPhrase_fin)
            else:
                 # ułóż listę atrybutów o odpowiedniej długości
                 attrs = [None] * fields.count()
                 if idx_gml != -1: attrs[idx_gml] = gmlid
                 if idx_kl  != -1: attrs[idx_kl]  = klasaDoRaportowania
                 if idx_kom != -1: attrs[idx_kom] = errorPhrase_fin
                 f.setAttributes(attrs)
            return f
        
    def zapewnijAtrybutyBledow(self, lyr):
            # Gwarantuje, że warstwy błędów mają pola: gml_id, klasa, komunikat.
            need = []
            flds = lyr.fields()
            for name in ('gml_id', 'klasa', 'komunikat'):
                if flds.indexFromName(name) == -1:
                    need.append(QgsField(name, QVariant.String))
            if need:
                pr = lyr.dataProvider()
                pr.addAttributes(need)
                lyr.updateFields()

