# -*- 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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
import os
import pathlib
import configparser
import re
import time
import hashlib
import binascii
import logging
import gc
import tracemalloc
import itertools
import zipfile
from .defusedxml import ElementTree as et


from collections import namedtuple, Counter
from datetime import datetime
from time import sleep
import pandas as pd
import matplotlib.pyplot
import xlwt
import sip
from osgeo import ogr,gdal
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import (
    QFileDialog,
    QProgressBar,
    QMessageBox,
    QAction,
    QLabel
)
from qgis.core import *
from qgis.gui import *
from qgis.utils import *
from openpyxl import Workbook
from openpyxl.styles import(
    Alignment,
    Border,
    Side,
    Font,
    PatternFill
)
from lxml.etree import (
    parse,
    XMLSchema
)
from .resources import *
from .utils import *
from .walidatorPlikowGML_EGIB import *


class KontroleTreeViewEventFilter(QObject):
    """Obsługuje skróty klawiaturowe i grupowe zaznaczanie kontroli w drzewie."""


    def __init__(self, plugin):
        """Zapamiętuje instancję wtyczki i ostatnio klikniętą kontrolę."""
        super().__init__(plugin.dlg)
        self.plugin = plugin
        self.ostatnia_kontrola = None


    def eventFilter(self, obj, event):
        """Przechwytuje Ctrl++/Ctrl+- oraz kliknięcie z Shift w drzewie kontroli."""
        if self.plugin.dlg is None or not hasattr(self.plugin.dlg, 'treeView'):
            return False
        
        tree_view = self.plugin.dlg.treeView
        
        if event.type() == QEvent.KeyPress and obj in (tree_view, tree_view.viewport()):
            if event.modifiers() & Qt.ControlModifier:
                key = event.key()
                if key in (Qt.Key_Plus, Qt.Key_Equal):
                    self.plugin.zmienRozmiarCzcionkiKontroli(1)
                    return True
        
                if key in (Qt.Key_Minus, Qt.Key_Underscore):
                    self.plugin.zmienRozmiarCzcionkiKontroli(-1)
                    return True
        
            return False
        
        if event.type() != QEvent.MouseButtonPress:
            return False
        
        if event.button() != Qt.LeftButton:
            return False
        
        index = tree_view.indexAt(event.pos())
        
        if not index.isValid():
            return False
        
        if self.plugin.modelKontroli is None:
            return False
        item = self.plugin.modelKontroli.itemFromIndex(index)
        
        if item is None or item.hasChildren() or not item.isCheckable():
            return False
        
        if event.modifiers() & Qt.ShiftModifier and self.ostatnia_kontrola is not None:
            stan = Qt.Unchecked if item.checkState() == Qt.Checked else Qt.Checked
            self.plugin.zaznaczZakresKontroli(self.ostatnia_kontrola, item, stan)
            self.ostatnia_kontrola = item
            return True
        
        self.ostatnia_kontrola = item
        return False


class walidatorPlikowGML(WalidatorEGIB):

    def __init__(self, iface):
        """Inicjalizuje obiekt i zapisuje ustawienia potrzebne do dalszej pracy."""
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n','walidatorPlikowGML_{}.qm'.format(locale)
        )
        super().__init__()
        self.dlg = None
        self.filewidgets_initialized = False
        self.first_start = True
        self.modelKontroli = None
        self.kontrole_tree_event_filter = None
        self.plikiZparsowane = []
        self.slownikBledow = {}
        self.liczbaKontroliDoWykonania = 0
        self.liczbaKontroliWykonanych = 0
        self.kontrolaZWynikiemPozytywnym = 0
        self.listaBledow = {}
        self.idkontroli_LiczbaBledow = {}
        self.progress = QProgressBar()
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)
        self.actions = []
        self.menu = self.tr(u'&Walidator plików GML')
        self.first_start = None
        global mainPath, config, dlg, version, blokady, sciezkiPlikowZrodlowych
        os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1'
        mainPath = (
            pathlib.Path(QgsApplication.qgisSettingsDirPath())
            /"python/plugins/Walidator_plikow_gml/"
        )
        ui_path = os.path.join(mainPath, 'walidatorPlikowGML_dialog_base.ui')
        config = configparser.ConfigParser()
        config.read(os.path.join(mainPath,'Walidator_plikow_gml.ini'))
        self.mainPath = mainPath
        self.config = config
        self.config_path = os.path.join(mainPath, 'Walidator_plikow_gml.ini')
        self.rozmiarCzcionkiKontroli = self.wczytajRozmiarCzcionkiKontroli()
        self.plikWybranychKontroli = None
        self.wczytywanieWybranychKontroli = False
        metadata_path = os.path.join(mainPath, 'metadata.txt')
        blokady = [
            0, 0, 0,
            0, 0, 0,
            0, 0, 0,
            0, 0
        ]
        sciezkiPlikowZrodlowych = []
        # Odczytanie wersji wtyczki z pliku metadata.txt
        version = ""
        if os.path.exists(metadata_path):
           with open(metadata_path, 'r') as metadata_file:
               for line in metadata_file:
                   if line.startswith('version='):
                       version = line.split('=')[1].strip()
                       break
        tree = et.parse(ui_path)
        root = tree.getroot()
        for widget in root.iter('widget'):
            if widget.get('name') == 'label_14':
                text_property = widget.find('property[@name="text"]')
                if text_property is not None:
                    text_property.find('string').text = version
        
        # Zapisz zmodyfikowany plik
        tree.write(ui_path)


    def tr(self, message):
        """Tłumaczy wskazany komunikat w kontekście wtyczki."""
        return QCoreApplication.translate('walidatorPlikowGML', message)


    def add_action(self,icon_path,text,callback,
                   enabled_flag=True,add_to_menu=True,
                   add_to_toolbar=True,status_tip=None,
                   whats_this=None,parent=None):
        """Tworzy akcję QGIS oraz dodaje ją do menu i paska narzędzi zgodnie z parametrami."""
        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)
        if status_tip is not None:
            action.setStatusTip(status_tip)
        
        if whats_this is not None:
            action.setWhatsThis(whats_this)
        
        if add_to_toolbar:
            self.iface.addToolBarIcon(action)
        
        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)
        self.actions.append(action)
        return action


    def initGui(self):
        """Inicjalizuje elementy interfejsu wtyczki w QGIS."""
        icon_path = ':/plugins/walidatorPlikowGML/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'walidatorPlikowGML'),
            callback=self.run,
            parent=self.iface.mainWindow())
        
        self.first_start = True


    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
        # spróbuj odpiąć wszystkie sygnały z action
            try:
                action.triggered.disconnect()
            except TypeError:
                pass
        
            try:
                self.iface.removePluginMenu(self.menu,action)
            except Exception as e:
                print(e)
        
            try:
                self.iface.removeToolBarIcon(action)
            except Exception as e:
                print(e)
            
        self.actions.clear()
        if hasattr(self, "dlg") and self.dlg is not None:
            try:
                self.dlg.close()
            except Exception as e:
                print(e)
            try:
                self.dlg.deleteLater()
            except Exception as e:
                print(e)
            self.dlg = None
        try:
            self.iface.messageBar().clearWidgets()
        except Exception as e:
            print(e)
        
        global modelKontroli
        modelKontroli = None
        
        global warstwyZbledamiWalidacji
        warstwyZbledamiWalidacji = []
        
        global warstwyBledowKontroliAtrybutow
        warstwyBledowKontroliAtrybutow = []
        
        global warstwyZbledamiGeometrii
        warstwyZbledamiGeometrii = []
        
        global progress
        progress = None


    def run(self):
        """Uruchamia okno wtyczki oraz przygotowuje walidację i zapis wyników."""
        global walidowanaBaza
        walidowanaBaza = [
            'EGIB','GESUT','BDOT500',
            'RCN','PRNG','BDOT10k',
            'BDOO','MGR','KARTO10k'
        ]
        formatRaportu = [
            'xls', 'xlsx', 'pdf',
            'txt', 'shp', 'gpkg'
        ]
        if self.dlg is None:
            from .walidatorPlikowGML_dialog import walidatorPlikowGMLDialog
            self.dlg = walidatorPlikowGMLDialog()
            self.dlg.comboBox.addItems(walidowanaBaza)
            self.dlg.mComboBox.addItems(formatRaportu)
            self.dlg.mComboBox.setCheckedItems(['xls'])
            self.dlg.pushButton.clicked.connect(self.zaznaczKontroleNaPodstawieDanych)
            self.dlg.pushButton_1.clicked.connect(self.zaznaczWszystkieKontrole)
            self.dlg.pushButton_2.clicked.connect(self.odznaczWszystkieKontrole)
            self.dlg.comboBox.currentIndexChanged.connect(self.uzupelnienieWyboruXSD)
            self.dlg.comboBox_xsd.currentIndexChanged.connect(self.zmianaXSD)
            self.dlg.comboBox.currentIndexChanged.connect(self.szarzenieNieBDOT)
            self.dlg.pushButton.setEnabled(False)
            self.uzupelnienieWyboruXSD(0)
            self.inicjalizujCheckBoxZapisuWybranychKontroli()
        self.walidacjaIKontrolaAtrybutow()
        self.zapisDoGPKG()


    def przygotujOknoPoOtwarciu(self):
        """Czyści wskazany plik i ustawia pierwszą zakładkę przy każdym otwarciu okna wtyczki."""
        global blokady
        if self.dlg is None:
            return
        
        if hasattr(self.dlg, 'mQgsFileWidget'):
            try:
                poprzedni_stan_sygnalow = self.dlg.mQgsFileWidget.blockSignals(True)
                self.dlg.mQgsFileWidget.setFilePath('')
                self.dlg.mQgsFileWidget.blockSignals(poprzedni_stan_sygnalow)
            except Exception as e:
                print(e)
        
        try:
            blokady[0] = 1
        except Exception as e:
            print(e)
        
        if hasattr(self.dlg, 'button_box'):
            try:
                self.dlg.button_box.buttons()[0].setEnabled(False)
            except Exception as e:
                print(e)
        
        try:
            for tab_widget in self.dlg.findChildren(QTabWidget):
                tab_widget.setCurrentIndex(0)
        except Exception as e:
            print(e)


    def szarzenieNieBDOT(self):
         """Dezaktywacja przycisku gdy jest wybrana opcja inna niż BDOT10k"""
         wybranaBaza = self.dlg.comboBox.currentText()
         if wybranaBaza != 'BDOT10k':
             self.dlg.pushButton.setEnabled(False)
         else:
             self.dlg.pushButton.setEnabled(True)


    def wczytanieWersjiSzablonow(self, nazwaBazyDanych):
        """Wczytuje dostępne wersje szablonów kontroli dla wskazanej bazy danych."""
        self.dlg.comboBox_2.currentIndexChanged.disconnect()
        szablonPath = os.path.join(mainPath, 'SzablonyKontroli', nazwaBazyDanych)
        self.dlg.comboBox_2.clear()
        for file in sorted(os.listdir(szablonPath),reverse = True):
            if file.endswith('.xml'):
                wersjaSzablonuKontroli = et.parse(
                    os.path.join(szablonPath, file)
                ).getroot().get('version')
                self.dlg.comboBox_2.addItem(wersjaSzablonuKontroli)
        self.dlg.comboBox_2.setCurrentIndex(0)
        self.wczytanieSzablonuKontroli()
        self.dlg.comboBox_2.currentIndexChanged.connect(self.wczytanieSzablonuKontroli)
        self.kontrolaIstnieniaPlikowGranicDlaBDOT10k()


    def sciezkaDoWskazanegoSzablonuKontroli(self, wskazanaWersjaSzablonu, nazwaBazyDanych):
        """Zwraca ścieżkę do pliku szablonu kontroli zgodnego z wybraną wersją."""
        szablonPath = os.path.join(mainPath, 'SzablonyKontroli', nazwaBazyDanych)
        for file in sorted(os.listdir(szablonPath), reverse = True):
            if file.endswith('.xml'):
                wersjaSzablonuKontroli = et.parse(
                    os.path.join(szablonPath, file)
                ).getroot().get('version')
                if wersjaSzablonuKontroli == wskazanaWersjaSzablonu:
                    return os.path.join(szablonPath, file)


    def uzupelnienieWyboruXSD(self, txt):
        """Uzupełnia listę dostępnych schematów XSD dla wybranej bazy danych."""
        global xsdPath
        nazwaBazyDanych = walidowanaBaza[txt]
        xsdPath_tmp = os.path.join(mainPath, 'XSD', nazwaBazyDanych)
        self.dlg.comboBox_xsd.clear()
        for file in sorted(os.listdir(xsdPath_tmp), reverse = True):
            if file.lower().endswith('.xsd'):
                self.dlg.comboBox_xsd.addItem(file)


    def zmianaXSD(self):
        """Aktualizuje ścieżkę i wersję aktualnie wybranego schematu XSD."""
        global xsdPath, wersjaSchematu
        xsdPath_tmp = os.path.join(mainPath, 'XSD', self.dlg.comboBox.currentText())
        xsdPath = os.path.join(xsdPath_tmp, self.dlg.comboBox_xsd.currentText())
        filename = os.path.basename(xsdPath)
        if filename != '':
            tree = et.parse(xsdPath)
            root = tree.getroot()
            wersjaSchematu = root.get('version')


    def zapiszPlikKonfiguracji(self):
        """Zapisuje bieżące ustawienia w pliku Walidator_plikow_gml.ini."""
        with open(self.config_path, 'w', encoding = 'utf-8') as configfile:
            self.config.write(configfile)


    def wczytajRozmiarCzcionkiKontroli(self):
        """Odczytuje z pliku INI rozmiar czcionki drzewa listy kontroli."""
        domyslny_rozmiar = 10
        minimalny_rozmiar = 7
        maksymalny_rozmiar = 24
        
        try:
            rozmiar = self.config.getint(
                'DEFAULT',
                'rozmiar_czcionki_kontroli',
                fallback = domyslny_rozmiar
            )
        except (ValueError, configparser.Error):
            rozmiar = domyslny_rozmiar
        
        return max(minimalny_rozmiar, min(maksymalny_rozmiar, rozmiar))


    def zapiszRozmiarCzcionkiKontroli(self):
        """Zapisuje w pliku INI bieżący rozmiar czcionki drzewa listy kontroli."""
        self.config.set(
            'DEFAULT',
            'rozmiar_czcionki_kontroli',
            str(self.rozmiarCzcionkiKontroli)
        )
        self.zapiszPlikKonfiguracji()


    def zastosujRozmiarCzcionkiKontroli(self):
        """Ustawia rozmiar czcionki dla nazw kontroli widocznych w treeView."""
        if self.dlg is None or not hasattr(self.dlg, 'treeView'):
            return
        
        font = self.dlg.treeView.font()
        font.setPointSize(self.rozmiarCzcionkiKontroli)
        self.dlg.treeView.setFont(font)
        self.dlg.treeView.viewport().setFont(font)
        self.dlg.treeView.doItemsLayout()
        self.dlg.treeView.viewport().update()


    def zmienRozmiarCzcionkiKontroli(self, krok):
        """Zmienia rozmiar czcionki nazw kontroli skrótami Ctrl++ oraz Ctrl+-."""
        minimalny_rozmiar = 7
        maksymalny_rozmiar = 24
        nowy_rozmiar = max(
            minimalny_rozmiar,
            min(maksymalny_rozmiar, self.rozmiarCzcionkiKontroli + krok)
        )
        
        if nowy_rozmiar == self.rozmiarCzcionkiKontroli:
            return
        
        self.rozmiarCzcionkiKontroli = nowy_rozmiar
        self.zastosujRozmiarCzcionkiKontroli()
        self.zapiszRozmiarCzcionkiKontroli()


    def inicjalizujCheckBoxZapisuWybranychKontroli(self):
        """Wczytuje stan checkBox_2 z konfiguracji i podłącza zapis jego zmian."""
        if not hasattr(self.dlg, 'checkBox_2'):
            return
        
        aktywny = self.config.getboolean(
            'DEFAULT',
            'zapisywanie_wybranych_kontroli',
            fallback = False
        )
        plik_wybranych = self.config.get(
            'DEFAULT',
            'plik_wybranych_kontroli',
            fallback = 'Wybrane_kontrole.ini'
        )
        self.plikWybranychKontroli = self.sciezkaPlikuWybranychKontroli(plik_wybranych)
        
        try:
            self.dlg.checkBox_2.stateChanged.disconnect(
                self.zmianaCheckBoxZapisuWybranychKontroli
            )
        except TypeError:
            pass
        
        self.dlg.checkBox_2.setChecked(aktywny)
        self.dlg.checkBox_2.stateChanged.connect(
            self.zmianaCheckBoxZapisuWybranychKontroli
        )


    def zmianaCheckBoxZapisuWybranychKontroli(self, state):
        """Zapisuje zmianę stanu checkBox_2 i ewentualnie bieżący wybór kontroli."""
        aktywny = state == Qt.Checked
        self.config.set(
            'DEFAULT',
            'zapisywanie_wybranych_kontroli',
            'true' if aktywny else 'false'
        )
        
        if not self.config.has_option('DEFAULT', 'plik_wybranych_kontroli'):
            self.config.set(
                'DEFAULT',
                'plik_wybranych_kontroli',
                'Wybrane_kontrole.ini'
            )
        
        self.zapiszPlikKonfiguracji()
        
        if aktywny:
            self.zapiszWybraneKontrole()


    def czyZapisywacWybraneKontrole(self):
        """Sprawdza, czy użytkownik włączył zapisywanie konfiguracji wybranych kontroli."""
        return hasattr(self.dlg, 'checkBox_2') and self.dlg.checkBox_2.isChecked()


    def sciezkaPlikuWybranychKontroli(self, nazwa_pliku = None):
        """Zwraca pełną ścieżkę pliku z konfiguracją zaznaczonych kontroli."""
        nazwa_pliku = nazwa_pliku or self.config.get(
            'DEFAULT',
            'plik_wybranych_kontroli',
            fallback = 'Wybrane_kontrole.ini'
        )
        
        if os.path.isabs(nazwa_pliku):
            return nazwa_pliku
        
        return os.path.join(self.mainPath, nazwa_pliku)


    def kluczKonfiguracjiWybranychKontroli(self):
        """Buduje nazwę sekcji konfiguracji dla aktualnej bazy i wersji szablonu kontroli."""
        baza = self.dlg.comboBox.currentText() if self.dlg is not None else ''
        szablon = getattr(self, 'wersjaSzablonuKontroli', '') or self.dlg.comboBox_2.currentText()
        xsd = self.dlg.comboBox_xsd.currentText() if self.dlg is not None else ''
        surowy_klucz = f'{baza}|{szablon}|{xsd}'
        bezpieczny_klucz = re.sub(r'[^0-9A-Za-z_.-]+', '_', surowy_klucz).strip('_')
        
        return f'kontrole_{bezpieczny_klucz}'


    def pobierzIdWybranychKontroli(self):
        """Zwraca identyfikatory aktualnie zaznaczonych kontroli potomnych."""
        ids_kontroli = []
        
        if self.modelKontroli is None:
            return ids_kontroli
        
        for i in range(self.modelKontroli.rowCount()):
            parent = self.modelKontroli.item(i)
        
            if parent is None:
                continue
        
            for j in range(parent.rowCount()):
                child = parent.child(j)
        
                if child is None or child.checkState() != Qt.Checked:
                    continue
        
                id_kontroli = child.data(1)
        
                if id_kontroli:
                    ids_kontroli.append(str(id_kontroli))
        
        return ids_kontroli


    def zapiszWybraneKontrole(self):
        """Zapisuje do osobnego pliku listę aktualnie zaznaczonych kontroli."""
        if self.wczytywanieWybranychKontroli or not self.czyZapisywacWybraneKontrole():
            return
        
        if self.modelKontroli is None:
            return
        
        sciezka = self.sciezkaPlikuWybranychKontroli()
        katalog = os.path.dirname(sciezka)
        
        if katalog and not os.path.exists(katalog):
            os.makedirs(katalog, exist_ok = True)
        
        config_kontroli = configparser.ConfigParser()
        config_kontroli.read(sciezka, encoding = 'utf-8')
        sekcja = self.kluczKonfiguracjiWybranychKontroli()
        
        if not config_kontroli.has_section(sekcja):
            config_kontroli.add_section(sekcja)
        
        config_kontroli.set(sekcja, 'baza', self.dlg.comboBox.currentText())
        config_kontroli.set(sekcja, 'wersja_szablonu', str(getattr(self, 'wersjaSzablonuKontroli', '')))
        config_kontroli.set(sekcja, 'xsd', self.dlg.comboBox_xsd.currentText())
        config_kontroli.set(sekcja, 'kontrole', ','.join(self.pobierzIdWybranychKontroli()))
        config_kontroli.set(sekcja, 'data_zapisu', time.strftime('%Y-%m-%d %H:%M:%S'))
        
        with open(sciezka, 'w', encoding = 'utf-8') as configfile:
            config_kontroli.write(configfile)


    def odtworzWybraneKontrole(self):
        """Odtwarza zaznaczenie kontroli zapisane dla aktualnej bazy i szablonu."""
        if not self.czyZapisywacWybraneKontrole() or self.modelKontroli is None:
            return
        
        sciezka = self.sciezkaPlikuWybranychKontroli()
        
        if not os.path.exists(sciezka):
            return
        
        config_kontroli = configparser.ConfigParser()
        config_kontroli.read(sciezka, encoding = 'utf-8')
        sekcja = self.kluczKonfiguracjiWybranychKontroli()
        
        if not config_kontroli.has_section(sekcja):
            return
        
        zapisane_ids = {
            identyfikator.strip()
            for identyfikator in config_kontroli.get(sekcja, 'kontrole', fallback = '').split(',')
            if identyfikator.strip()
        }
        
        if not zapisane_ids:
            return
        
        self.wczytywanieWybranychKontroli = True
        
        try:
            for i in range(self.modelKontroli.rowCount()):
                parent = self.modelKontroli.item(i)
        
                if parent is None:
                    continue
        
                for j in range(parent.rowCount()):
                    child = parent.child(j)
        
                    if child is None:
                        continue
        
                    if str(child.data(1)) in zapisane_ids:
                        child.setCheckState(Qt.Checked)
                    else:
                        child.setCheckState(Qt.Unchecked)
        finally:
            self.wczytywanieWybranychKontroli = False


    def podlaczGrupoweZaznaczanieKontroli(self):
        """Podłącza obsługę Shift oraz Ctrl++/Ctrl+- w drzewie listy kontroli."""
        if self.kontrole_tree_event_filter is not None:
            for obiekt in (self.dlg.treeView, self.dlg.treeView.viewport()):
                try:
                    obiekt.removeEventFilter(self.kontrole_tree_event_filter)
                except RuntimeError:
                    pass
        
        self.kontrole_tree_event_filter = KontroleTreeViewEventFilter(self)
        self.dlg.treeView.installEventFilter(self.kontrole_tree_event_filter)
        self.dlg.treeView.viewport().installEventFilter(self.kontrole_tree_event_filter)


    def listaKontroliWDrzewie(self):
        """Zwraca listę kontroli potomnych w kolejności widocznej w drzewie."""
        kontrole = []
        
        if self.modelKontroli is None:
            return kontrole
        
        for i in range(self.modelKontroli.rowCount()):
            parent = self.modelKontroli.item(i)
        
            if parent is None:
                continue
        
            for j in range(parent.rowCount()):
                child = parent.child(j)
        
                if child is not None and child.isCheckable():
                    kontrole.append(child)
        
        return kontrole


    def zaznaczZakresKontroli(self, item_start, item_end, stan):
        """Ustawia wskazany stan zaznaczenia dla kolejnych kontroli z wybranego zakresu."""
        kontrole = self.listaKontroliWDrzewie()
        
        try:
            start = kontrole.index(item_start)
            end = kontrole.index(item_end)
        except ValueError:
            return
        
        if start > end:
            start, end = end, start
        
        for item in kontrole[start:end + 1]:
            item.setCheckState(stan)


    def sprawdz_zip_i_wyodrebnij_zwiniete(self, lista_sciezek):
        """
        Sprawdza, czy którykolwiek plik .gml/.xml w ZIP zawiera zwinięte rekordy.
        Jeśli tak, pokazuje ostrzeżenie. Zwraca pierwszą wypakowaną ścieżkę do walidacji.
        """
        try:
            bledne_pliki = []
            for pelna_sciezka in lista_sciezek:
                if not self.sprawdz_dlugosc_linii(pelna_sciezka, tylko_sprawdzaj=True):
                    bledne_pliki.append(os.path.basename(pelna_sciezka))
        
            if bledne_pliki:
                if len(bledne_pliki) == 1:
                    tekst = (
                        f"Znaleziono zwinięty rekord lub rekordy w następującym pliku:\n\n"
                        "{bledne_pliki[0]}"
                    )
                else:
                    tekst =(
                        "Znaleziono zwinięte rekord lub rekordy w następujących plikach:\n\n"
                        + "\n".join(bledne_pliki)
                    )
                tekst += (
                    "\n\nW przypadku błędu może zostać wskazany nieprawidłowy"
                    " numer wiersza w raporcie XLS.\n\nCzy chcesz kontynuować?"
                )
                try:
                    ret = QMessageBox.warning(
                        QMessageBox(),
                        "Ostrzeżenie",
                        tekst,
                        QMessageBox.Ok | QMessageBox.Cancel
                        )
                    if ret != QMessageBox.Ok:
                        return False
                except Exception as e:
                    QMessageBox.critical(QMessageBox(), 'Błąd',
                                         f'Błąd podczas wyświetlania komunikatu ostrzegawczego: {e}',
                                         QMessageBox.Ok
                                         )
                    return False
        
            return lista_sciezek[0]  # Pierwszy do walidacji
        
        except Exception as e:
            QMessageBox.critical(QMessageBox(),
                                 'Uwaga!',
                                 f'Błąd rozpakowywania ZIP: {e}',
                                 QMessageBox.Ok
                                 )
            return None


    def sprawdz_dlugosc_linii(self, input_path, tylko_sprawdzaj=False):
          """Sprawdza, czy plik GML lub XML zawiera zwinięte rekordy utrudniające raportowanie błędów."""
          if not input_path.lower().endswith(('.xml', '.gml')):
            return True
          try:
              licznik_zwinietych = 0
              try:
                  with open(input_path, 'r', encoding='utf-8') as f:
                      linie = f.readlines()
              except UnicodeDecodeError:
                  with open(input_path, 'r', encoding='windows-1250') as f:
                      linie = f.readlines()
              for linia in linie:
                  if '</gml:featureMember>' in linia and linia.count('><') >= 1:
                      licznik_zwinietych += 1
                      if licznik_zwinietych >= 1: # liczba_zbitek:
                          break
              if licznik_zwinietych >= 1: # liczba_zbitek:
                   if tylko_sprawdzaj:
                      return False
                   try:
                       ret = QMessageBox.warning(
                           QMessageBox(),
                           'Ostrzeżenie',
                           (
                               'Plik zawiera zwinięty przynajmniej jeden rekord!\n\n'
                               'W przypadku błędu może zostać wskazany nieprawidłowy numer wiersza w raporcie XLS.'
                               '\n\nCzy chcesz kontynuować?'
                           ),
                           QMessageBox.Ok | QMessageBox.Cancel
                       )
                       if ret != QMessageBox.Ok:
                           return False
                   except Exception as e:
                         QMessageBox.critical(
                             QMessageBox(),
                             'Błąd',
                             f'Błąd podczas wyświetlania komunikatu ostrzegawczego: {e}',
                             QMessageBox.Ok
                         )
                         return False
              return True
        
          except Exception as e:
              QMessageBox.critical(
                  QMessageBox(),
                  'Błąd',
                  f'Błąd sprawdzania pliku: {e}',
                  QMessageBox.Ok
              )
          return False


    def wyborPliku(self, txt):
        """Obsługuje wskazanie głównego pliku do walidacji i rozpoznaje typ walidowanej bazy."""
        global files, plikZIP, blokady, plikDoWeryfikacji, wypakowane_sciezki
        files = []
        wypakowane_sciezki = []
        plikDoWeryfikacji = None
        if os.path.isfile(txt):
            if self.dlg.mQgsFileWidget_dz.filePath() == self.dlg.mQgsFileWidget.filePath():
                QMessageBox.critical(
                    QMessageBox(),
                    'Uwaga!',
                    'Wybrano ten sam plik co w zakładce "Dane źródłowe".',
                    QMessageBox.Ok
                )
            self.dlg.button_box.buttons()[0].setEnabled(True)
            if zipfile.is_zipfile(txt):
                try:
                    plikZIP = zipfile.ZipFile(txt,'r')
                except:
                    self.iface.messageBar().pushMessage(
                        "Uwaga!",
                        "Niepoprawny plik zip.",
                        level = Qgis.Critical
                    )
                    return
                files = [
                    f for f in plikZIP.namelist()
                    if (
                        f.endswith('.gml')
                        or f.endswith('.xml')
                        or f.endswith('.GML')
                        or f.endswith('.XML')
                    )
                ]
                if files:
                    nazwaPlikuLubPlikow = ''.join(files)
            else:
                files.append(txt)
                nazwaPlikuLubPlikow = pathlib.Path(files[0]).name
        
            if not zipfile.is_zipfile(txt):
                plikDoWeryfikacji = open(txt, 'r', encoding='utf-8')
            else:
                try:
                    for f in files:
                        pelna_sciezka = plikZIP.extract(f, os.path.dirname(txt))
                        wypakowane_sciezki.append(pelna_sciezka)
                        # przekazanie sciezek do kontroli zwiniecia
                    sciezka_rozpakowanego = wypakowane_sciezki[0]
                    plikDoWeryfikacji = open(sciezka_rozpakowanego, 'r', encoding='utf-8')
                except:
                    QMessageBox.critical(
                        QMessageBox(),
                        'Uwaga!',
                        'Brak uprawnień do zapisu w katalogu wskazanego pliku.',
                         QMessageBox.Ok
                    )
            combo = self.dlg.comboBox
            for i in range(50):
                try:
                    line = plikDoWeryfikacji.readline().strip()
                except:
                    line = ''
                if 'BDOT10k_BDOO' in line:
                    combo.setCurrentIndex( combo.findText('BDOT10k'))
                    break
                elif '<ges2021:GES_' in line or '<ges:GES' in line:
                    combo.setCurrentIndex( combo.findText('GESUT'))
                    break
                elif 'MapaGlebowoRolnicza.xsd' in line:
                    combo.setCurrentIndex( combo.findText('MGR'))
                    break
                elif '<ok:OK' in line or 'urn:gugik:specyfikacje:gmlas:karto:1.0' in line:
                    combo.setCurrentIndex( combo.findText('KARTO10k'))
                    break
                elif '<ot2021:OT_' in line or '<ot:OT_' in line:
                    combo.setCurrentIndex( combo.findText('BDOT500'))
                    break
                else:
                    match = re.search(r"EGIB[-_]?1[._](\d+)[._]XSD", line.upper())
                    if match:
                        combo.setCurrentIndex( combo.findText('EGIB'))
                        n = int(match.group(1))
                        if 6 <= n <= 11:
                            self.dlg.comboBox_xsd.setCurrentIndex(
                                self.dlg.comboBox_xsd
                                .findText(f'EGIB_1.{n}.xsd')
                            )
            self.wczytanieWersjiSzablonow(self.dlg.comboBox.currentText())
            blokady[0] = 0
            if sum(blokady) == 0:
                self.dlg.button_box.buttons()[0].setEnabled(True)
            else:
                self.dlg.button_box.buttons()[0].setEnabled(False)
        else:
            blokady[0] = 1
            self.dlg.button_box.buttons()[0].setEnabled(False)


    def wyborPlikuPRNGmiejscowosci(self, txt):
        """Obsługuje wskazanie pliku źródłowego PRNG miejscowości."""
        global sciezkiPlikowZrodlowych
        if os.path.isfile(txt):
            if self.dlg.mQgsFileWidget_prng_m.filePath() == self.dlg.mQgsFileWidget.filePath():
                QMessageBox.critical(
                    QMessageBox(),
                    'Uwaga!',
                    'Wybrano ten sam plik co w zakładce "Walidacja plików GML".',
                    QMessageBox.Ok
                )
            if os.path.isfile(txt) and os.path.splitext(txt)[1].lower() == '.xml':
                 sciezkiPlikowZrodlowych.append(txt)
            blokady[6] = 0
        try:
              for i in range(modelKontroli.rowCount()):
                  item = modelKontroli.item(i)
                  if item.checkState() in (2,1):
                      for c in range(item.rowCount()):
                          if (
                              item.child(c).checkState() == 2
                              and item.child(c).data(6)
                              .__contains__('prng_miejscowosci')
                          ):
                              if os.path.isfile(txt):
                                  if sum(blokady) == 0:
                                      self.dlg.button_box.buttons()[0].setEnabled(True)
                              else:
                                  self.dlg.button_box.buttons()[0].setEnabled(False)
        except Exception as e:
            print('Błąd w funkcji wyborPlikuPRNGmiejscowosci:', e)
        config.set('DEFAULT', 'prng_miejscowosci', txt)
        with open(str(mainPath) + '/Walidator_plikow_gml.ini', 'w') as configfile:
            config.write(configfile)


    def wyborPlikuPRNGobiektowfizjograficznych(self, txt):
        """Obsługuje wskazanie pliku źródłowego PRNG obiektów fizjograficznych."""
        global sciezkiPlikowZrodlowych
        if os.path.isfile(txt):
            if self.dlg.mQgsFileWidget_prng_o.filePath() == self.dlg.mQgsFileWidget.filePath():
                QMessageBox.critical(
                    QMessageBox(),
                    'Uwaga!',
                    'Wybrano ten sam plik co w zakładce "Walidacja plików GML".',
                    QMessageBox.Ok
                )
            if os.path.isfile(txt) and os.path.splitext(txt)[1].lower() == '.xml':
                 sciezkiPlikowZrodlowych.append(txt)
            blokady[7] = 0
        try:
              for i in range(modelKontroli.rowCount()):
                  item = modelKontroli.item(i)
                  if item.checkState() in (2,1):
                      for c in range(item.rowCount()):
                          if (
                              item.child(c).checkState() == 2
                              and item.child(c).data(6)
                              .__contains__('prng_obiektyfizjograficzne')
                              ):
                              if os.path.isfile(txt):
                                  if sum(blokady) == 0:
                                      self.dlg.button_box.buttons()[0].setEnabled(True)
                              else:
                                  self.dlg.button_box.buttons()[0].setEnabled(False)
        except Exception as e:
            print('Błąd w funkcji wyborPlikuPRNGobiektowfizjograficznych:',e)
        config.set('DEFAULT', 'prng_obiektyfizjograficzne', txt)
        with open(str(mainPath)+'/Walidator_plikow_gml.ini', 'w') as configfile:
            config.write(configfile)


    def wyborPlikuTerenyChronione(self, txt):
        """Obsługuje wskazanie pliku ZIP z danymi terenów chronionych."""
        global sciezkiPlikowZrodlowych, plikZIPzrodlowy
        if os.path.isfile(txt):
            if self.dlg.mQgsFileWidget_tc.filePath() == self.dlg.mQgsFileWidget.filePath():
                QMessageBox.critical(
                    QMessageBox(),
                    'Uwaga!',
                    'Wybrano ten sam plik co w zakładce "Walidacja plików GML".',
                    QMessageBox.Ok
                )
            if zipfile.is_zipfile(txt):
                    try:
                        plikZIPzrodlowy = zipfile.ZipFile(txt,'r')
                    except:
                        self.iface.messageBar().pushMessage(
                            "Uwaga!",
                            "Niepoprawny plik zip.",
                            level = Qgis.Critical
                        )
                        return
                    pliki = plikZIPzrodlowy.namelist()
                    for plik in pliki:
                        if os.path.splitext(plik)[1].lower() == '.shp':
                            sciezkiPlikowZrodlowych.append(plik)
            else:
                 if os.path.isfile(txt) and os.path.splitext(txt)[1].lower() == '.shp':
                  sciezkiPlikowZrodlowych.append(txt)
            blokady[9] = 0
        try:
              for i in range(modelKontroli.rowCount()):
                  item = modelKontroli.item(i)
                  if item.checkState() in (2, 1):
                      for c in range(item.rowCount()):
                          if (
                              item.child(c).checkState() == 2
                              and item.child(c).data(6)
                              .__contains__('tereny_chronione')
                          ):
                              if os.path.isfile(txt):
                                  if sum(blokady) == 0:
                                      self.dlg.button_box.buttons()[0].setEnabled(True)
                              else:
                                  self.dlg.button_box.buttons()[0].setEnabled(False)
        except Exception as e:
            print('Błąd w funkcji wyborPlikuTerenyChronione:',e)
        config.set('DEFAULT', 'tereny_chronione', txt)
        with open(str(mainPath)+'/Walidator_plikow_gml.ini', 'w') as configfile:
            config.write(configfile)


    def wyborPlikuULIC(self, txt):
        """Obsługuje wskazanie pliku CSV z danymi TERYT ULIC."""
        global sciezkiPlikowZrodlowych
        if os.path.isfile(txt):
            if os.path.isfile(txt) and os.path.splitext(txt)[1].lower() == '.csv':
                  sciezkiPlikowZrodlowych.append(txt)
            blokady[8] = 0
        try:
             for i in range(modelKontroli.rowCount()):
                 item = modelKontroli.item(i)
                 if item.checkState() in (2,1):
                     for c in range(item.rowCount()):
                         if (
                             item.child(c).checkState() == 2
                             and item.child(c).data(6)
                             .__contains__('ulic_gus')
                          ):
                             if os.path.isfile(txt):
                                 if sum(blokady) == 0:
                                     self.dlg.button_box.buttons()[0].setEnabled(True)
                             else:
                                 self.dlg.button_box.buttons()[0].setEnabled(False)
        except Exception as e:
             print('Błąd w funkcji wyborPlikuULIC:',e)
        config.set('DEFAULT', 'ulic_gus', txt)
        with open(str(mainPath)+'/Walidator_plikow_gml.ini', 'w') as configfile:
            config.write(configfile)


    def wyborPlikuSIMC(self, txt):
        """Obsługuje wskazanie pliku CSV z danymi TERYT SIMC."""
        global sciezkiPlikowZrodlowych
        if os.path.isfile(txt):
            if os.path.splitext(txt)[1].lower() == '.csv':
                if self.dlg.mQgsFileWidget_ulic.filePath() == txt:
                    QMessageBox.critical(
                        QMessageBox(),
                        'Uwaga!',
                        'Wybrano ten sam plik co w Dane ULIC.',
                        QMessageBox.Ok
                    )
                else:
                    sciezkiPlikowZrodlowych.append(txt)
            blokady[10] = 0
        try:
             for i in range(modelKontroli.rowCount()):
                 item = modelKontroli.item(i)
                 if item.checkState() in (2,1):
                     for c in range(item.rowCount()):
                         if (
                             item.child(c).checkState() == 2
                             and item.child(c).data(6)
                             .__contains__('simc_gus')
                          ):
                             if os.path.isfile(txt):
                                 if sum(blokady) == 0:
                                     self.dlg.button_box.buttons()[0].setEnabled(True)
                             else:
                                 self.dlg.button_box.buttons()[0].setEnabled(False)
        except Exception as e:
             print('Błąd w funkcji wyborPlikuSIMC:', e)
        config.set('DEFAULT', 'simc_gus', txt)
        with open(str(mainPath) + '/Walidator_plikow_gml.ini', 'w') as configfile:
            config.write(configfile)


    def wyborPlikuGranicPowiatow(self, txt):
        """Obsługuje wskazanie pliku GML z granicami powiatów."""
        global sciezkiPlikowZrodlowych
        if os.path.isfile(txt):
            if self.dlg.mQgsFileWidget_gp.filePath() == self.dlg.mQgsFileWidget.filePath():
                QMessageBox.critical(
                    QMessageBox(),
                    'Uwaga!',
                    'Wybrano ten sam plik co w zakładce "Walidacja plików GML".',
                    QMessageBox.Ok
                )
            if os.path.isfile(txt) and os.path.splitext(txt)[1].lower() == '.gml':
                  sciezkiPlikowZrodlowych.append(txt)
            blokady[2] = 0
        try:
             for i in range(modelKontroli.rowCount()):
                 item = modelKontroli.item(i)
                 if item.checkState() in (2,1):
                     for c in range(item.rowCount()):
                         if (
                            item.child(c).checkState() == 2
                             and any(
                                 keyword in item.data(6)
                                 for keyword in ["Granice_powiatow",
                                                 "czyObiektyWewnatrzPowiatu",
                                                 "minimalna",
                                                 "minDlugosc",
                                                 "kontrolaZgodnosciZDanymiGDOS"
                                                ]
                                 )
                             ):
                             if os.path.isfile(txt):
                                 if sum(blokady) == 0:
                                     self.dlg.button_box.buttons()[0].setEnabled(True)
                             else:
                                 self.dlg.button_box.buttons()[0].setEnabled(False)
        except Exception as e:
             print('Błąd w funkcji wyborPlikuGranicPowiatow:',e)
        config.set('DEFAULT', 'granicePowiatow', txt)
        with open(str(mainPath)+'/Walidator_plikow_gml.ini', 'w') as configfile:
            config.write(configfile)
        self.kontrolaIstnieniaPlikowGranicDlaBDOT10k()


    def wyborPlikuGranicGmin(self, txt):
        """Obsługuje wskazanie pliku GML z granicami gmin."""
        config.set('DEFAULT', 'graniceGmin', txt)
        with open(str(mainPath)+'/Walidator_plikow_gml.ini', 'w') as configfile:
            config.write(configfile)
        self.kontrolaIstnieniaPlikowGranicDlaBDOT10k()


    def wyborPlikuGranicJednostekEwidencyjnych(self, txt):
        """Obsługuje wskazanie pliku GML z granicami jednostek ewidencyjnych."""
        config.set('DEFAULT', 'graniceJednostekEwidencyjnych', txt)
        with open(str(mainPath)+'/Walidator_plikow_gml.ini', 'w') as configfile:
            config.write(configfile)
        self.kontrolaIstnieniaPlikowGranicDlaBDOT10k()


    def wyborPlikuZrodlowego(self, txt):
        """Obsługuje wskazanie dodatkowego pliku źródłowego używanego w kontrolach."""
        global sciezkiPlikowZrodlowych, plikZIPzrodlowy
        if os.path.isfile(txt):
            if self.dlg.mQgsFileWidget_dz.filePath() == self.dlg.mQgsFileWidget.filePath():
                QMessageBox.critical(
                    QMessageBox(),
                    'Uwaga!',
                    'Wybrano ten sam plik co w zakładce "Walidacja plików GML".',
                    QMessageBox.Ok
                )
            if zipfile.is_zipfile(txt):
                try:
                    plikZIPzrodlowy = zipfile.ZipFile(txt,'r')
                except:
                    self.iface.messageBar().pushMessage(
                        "Uwaga!",
                        "Niepoprawny plik zip.",
                        level = Qgis.Critical
                    )
                    return
                pliki = plikZIPzrodlowy.namelist()
                for plik in pliki:
                    if os.path.splitext(plik)[1].lower() in ['.xml','.gml']:
                        sciezkiPlikowZrodlowych.append(plik)
            else:
                if os.path.isfile(txt) and os.path.splitext(txt)[1].lower() in ['.xml','.gml']:
                    sciezkiPlikowZrodlowych.append(txt)
            blokady[5] = 0
        try:
            for i in range(modelKontroli.rowCount()):
                item = modelKontroli.item(i)
                if item.checkState() in (2,1):
                    for c in range(item.rowCount()):
                        if (
                            item.child(c).checkState() == 2
                            and item.child(c).data(6)
                            .__contains__('(gml,gml)')
                         ):
                            if os.path.isfile(txt):
                                if sum(blokady) == 0:
                                    self.dlg.button_box.buttons()[0].setEnabled(True)
                            else:
                                self.dlg.button_box.buttons()[0].setEnabled(False)
        except Exception as e:
             print('Błąd w funkcji wyborPlikuZrodlowego:',e)


    def kontrolaIstnieniaPlikowGranicDlaBDOT10k(self):
        """Sprawdza, czy dla wybranych kontroli BDOT10k wskazano wymagane pliki granic."""
        if self.dlg.comboBox.currentText() == 'BDOT10k':
            granicePowiatow = config['DEFAULT']['granicePowiatow']
            graniceGmin = config['DEFAULT']['graniceGmin']
            graniceJednostekEwidencyjnych = config['DEFAULT']['graniceJednostekEwidencyjnych']
        
            if granicePowiatow == "":
                for i in range(modelKontroli.rowCount()):
                    item = modelKontroli.item(i)
                    if item.checkState() in (1,2):
                        for child in range(item.rowCount()):
                            if (item.child(child).checkState() == 2
                                and (item.child(child).data(6)
                                     .__contains__("czyObiektyWewnatrzPowiatu")
                                     or item.child(child).data(6)
                                     .__contains__("Granice_powiatow")
                                     or item.child(child).data(6)
                                     .__contains__("minimalna")
                                     or item.child(child).data(6)
                                     .__contains__("minDlugosc")
                                     or item.child(child).data(6)
                                     .__contains__("fullCoverage")
                                     or item.child(child).data(6)
                                     .__contains__("kontrolaZgodnosciZDanymiGDOS"))
                                ):
                                blokady[2] = 1
                                self.dlg.button_box.buttons()[0].setEnabled(False)
                                return
                            else:
                                blokady[2] = 0
            if graniceGmin == "":
                for i in range(modelKontroli.rowCount()):
                    item = modelKontroli.item(i)
                    if item.checkState() in (1,2):
                        for child in range(item.rowCount()):
                            if (
                                item.child(child).checkState() == 2
                                and item.child(child).data(6)
                                .__contains__("Granice_gmin")
                             ):
                                blokady[3] = 1
                                self.dlg.button_box.buttons()[0].setEnabled(False)
                                return
                            else:
                                blokady[3] = 0
            if graniceJednostekEwidencyjnych == "":
                for i in range(modelKontroli.rowCount()):
                    item = modelKontroli.item(i)
                    if item.checkState() in (1, 2):
                        for child in range(item.rowCount()):
                            if (
                                item.child(child).checkState() == 2
                                and item.child(child).data(6)
                                .__contains__("Granice_jednostek_ewidencyjnych")
                             ):
                                blokady[4] = 1
                                self.dlg.button_box.buttons()[0].setEnabled(False)
                                return
                            else:
                                blokady[4] = 0
        
            if self.dlg.mQgsFileWidget.filePath() != '':
                self.dlg.button_box.buttons()[0].setEnabled(True)


    # wskazanie pliku i zlecenie walidacji


    def walidacjaIKontrolaAtrybutow(self):
        """Steruje wyborem danych, walidacją GML oraz wykonaniem kontroli atrybutowych i geometrycznych."""
        global pliki, sciezkaGML, files, plik, path, plikRaportu, walidacjaZWynikiemPozytywnym
        global kontrolaZWynikiemPozytywnym, xmlschema, formatPlikuRaportu, dataFrame
        global walidowanePliki_df, wiersze_df, opisyBledow_df, komunikatyBledow_df
        global gmlid_df_w, grupyKontroli_df, kontrolowanePliki_df, klasy_df, gmlid_df_k
        global komunikatyBledowKontroli_df, plikiZparsowane, slownikBledow
        global warstwyBledowKontroliAtrybutow, warstwyBledowWalidacji, sumaKontrolaPlikWalidowany
        global progress, liczbaKontroliWykonanych, nazwy, unikalny_id, nazwa_pliku
        global timestr, version, idkontroli_LiczbaBledow, idkontroli
        global granicePowiatow, graniceGmin, graniceJednostekEwidencyjnych
        global kontroleWykonaniePojedyncze, plikZIP, pathSHP, pathGPKG
        global ulic_gus, simc_gus, prng_obiektyfizjograficzne, prng_miejscowosci, tereny_chronione
        
        sciezkaGML_ini = config['DEFAULT']['sciezkagml']
        granicePowiatow = config['DEFAULT']['granicePowiatow']
        graniceGmin = config['DEFAULT']['graniceGmin']
        graniceJednostekEwidencyjnych = config['DEFAULT']['graniceJednostekEwidencyjnych']
        ulic_gus = config['DEFAULT']['ulic_gus']
        simc_gus = config['DEFAULT']['simc_gus']
        prng_miejscowosci  = config['DEFAULT']['prng_miejscowosci']
        prng_obiektyfizjograficzne  = config['DEFAULT']['prng_obiektyfizjograficzne']
        tereny_chronione = config['DEFAULT']['tereny_chronione']
        kontroleWykonaniePojedyncze = {"topo_e3_k6":False,"topo_e5_k1":False}
        
        self.dlg.mQgsFileWidget.setFilter("Plik GML (*.gml);;Plik XML (*.xml);;Plik skompresowany (*.zip)")
        self.dlg.mQgsFileWidget_gp.setFilter("Plik GML (*.gml)")
        self.dlg.mQgsFileWidget_gg.setFilter("Plik GML (*.gml)")
        self.dlg.mQgsFileWidget_gje.setFilter("Plik GML (*.gml)")
        self.dlg.mQgsFileWidget_dz.setFilter("Plik skompresowany (*.zip);;Plik GML (*.gml);;Plik XML (*.xml)")
        self.dlg.mQgsFileWidget_prng_m.setFilter("Plik XML (*.xml)")
        self.dlg.mQgsFileWidget_prng_o.setFilter("Plik XML (*.xml)")
        self.dlg.mQgsFileWidget_tc.setFilter("Plik ZIP (*.zip)")
        self.dlg.mQgsFileWidget_ulic.setFilter("Plik CSV (*.csv)")
        self.dlg.mQgsFileWidget_simc.setFilter("Plik CSV (*.csv)")
        self.dlg.mQgsFileWidget_gp.setFilePath(granicePowiatow)
        self.dlg.mQgsFileWidget_gg.setFilePath(graniceGmin)
        self.dlg.mQgsFileWidget_gje.setFilePath(graniceJednostekEwidencyjnych)
        self.dlg.mQgsFileWidget_prng_m.setFilePath(prng_miejscowosci)
        self.dlg.mQgsFileWidget_prng_o.setFilePath(prng_obiektyfizjograficzne)
        self.dlg.mQgsFileWidget_ulic.setFilePath(ulic_gus)
        self.dlg.mQgsFileWidget_simc.setFilePath(simc_gus)
        self.dlg.mQgsFileWidget_tc.setFilePath(tereny_chronione)
        self.dlg.mQgsFileWidget.setDialogTitle("Wskaż plik z danymi do walidacji")
        self.dlg.mQgsFileWidget_gp.setDialogTitle("Wskaż plik z granicami powiatów")
        self.dlg.mQgsFileWidget_gg.setDialogTitle("Wskaż plik z granicami gmin")
        self.dlg.mQgsFileWidget_gje.setDialogTitle("Wskaż plik z granicami jednostek ewidencyjnych")
        self.dlg.mQgsFileWidget_dz.setDialogTitle("Wskaż plik z danymi źródłowymi")
        self.dlg.mQgsFileWidget_prng_m.setDialogTitle("Wskaż plik z PRNG miejscowości")
        self.dlg.mQgsFileWidget_prng_o.setDialogTitle("Wskaż plik z PRNG obiektów fizjograficznych")
        self.dlg.mQgsFileWidget_ulic.setDialogTitle("Wskaż plik z TERYT ULIC")
        self.dlg.mQgsFileWidget_simc.setDialogTitle("Wskaż plik z TERYT SIMC")
        self.dlg.mQgsFileWidget_tc.setDialogTitle("Wskaż plik ZIP z danymi SHP w zakresie terenów chronionych")
        self.dlg.mQgsFileWidget.setDefaultRoot(sciezkaGML_ini)
        if not self.filewidgets_initialized:
            self.dlg.mQgsFileWidget.fileChanged.connect(self.wyborPliku)
            self.dlg.mQgsFileWidget_gp.fileChanged.connect(self.wyborPlikuGranicPowiatow)
            self.dlg.mQgsFileWidget_gg.fileChanged.connect(self.wyborPlikuGranicGmin)
            self.dlg.mQgsFileWidget_gje.fileChanged.connect(self.wyborPlikuGranicJednostekEwidencyjnych)
            self.dlg.mQgsFileWidget_dz.fileChanged.connect(self.wyborPlikuZrodlowego)
            self.dlg.mQgsFileWidget_prng_m.fileChanged.connect(self.wyborPlikuPRNGmiejscowosci)
            self.dlg.mQgsFileWidget_prng_o.fileChanged.connect(self.wyborPlikuPRNGobiektowfizjograficznych)
            self.dlg.mQgsFileWidget_ulic.fileChanged.connect(self.wyborPlikuULIC)
            self.dlg.mQgsFileWidget_simc.fileChanged.connect(self.wyborPlikuSIMC)
            self.dlg.mQgsFileWidget_tc.fileChanged.connect(self.wyborPlikuTerenyChronione)
        
            self.dlg.comboBox.currentIndexChanged.connect(lambda: self.wczytanieWersjiSzablonow(self.dlg.comboBox.currentText()))
            self.dlg.comboBox_2.currentIndexChanged.connect(self.wczytanieSzablonuKontroli)
            self.dlg.mComboBox.checkedItemsChanged.connect(self.kontrolaWybranychFormatowRaportow)
            self.filewidgets_initialized = True
        
        self.przygotujOknoPoOtwarciu()
        result = self.dlg.exec()
        if not result:
            return
        # Sprawdzenie, czy plik nie zawiera długiej linii lub zwiniętych znaczników
        sciezka_wejsciowa = self.dlg.mQgsFileWidget.filePath()
        plik = [sciezka_wejsciowa, self.dlg.mQgsFileWidget.filter()]
        sciezkaGML = str(pathlib.Path(plik[0]).parent)
        
        if zipfile.is_zipfile(sciezka_wejsciowa):
            sciezka_do_walidacji = self.sprawdz_zip_i_wyodrebnij_zwiniete(wypakowane_sciezki)
        else:
            sciezka_do_walidacji = (
                sciezka_wejsciowa
                if self.sprawdz_dlugosc_linii(sciezka_wejsciowa)
                else None
                )
        
        if not sciezka_do_walidacji:
            return  # Błąd lub brak GML/XML
        
        if sciezkaGML == '.':
            QMessageBox.critical(
                QMessageBox(),
                'Uwaga!',
                'Nie wskazano pliku.',
                QMessageBox.Ok
            )
            return
        
        try:
            if not os.access(sciezkaGML, os.W_OK):
                self.iface.messageBar().pushMessage(
                    "Uwaga!",
                    "Brak uprawnień do zapisu raportu.",
                    level=Qgis.Critical
                )
                return
            nazwa_pliku = pathlib.Path(plik[0]).name[:-4]
            if sciezkaGML != sciezkaGML_ini:
                config.set('DEFAULT', 'sciezkagml', sciezkaGML)
                with open(str(mainPath)+'/Walidator_plikow_gml.ini', 'w') as configfile:
                    config.write(configfile)
        except Exception as inst:
            if inst.args[0] == 13:
                self.iface.messageBar().pushMessage(
                    "Uwaga!",
                    inst.args[1],
                    level=Qgis.Critical
                )
            else:
                print(f"bład w funkcji walidacjaIKontrolaAtrybutow: {inst}")
            return
        
        crc = 0
        with open(str(pathlib.Path(plik[0])), 'rb') as f:
              while True:
                  data = f.read(65536)
                  if not data:
                      break
                  crc = binascii.crc32(data, crc)
        sumaKontrolaPlikWalidowany = "%08X" % (crc & 0xFFFFFFFF)
        
        sumaKontrolnaDaneZrodlowe = 'nie dotyczy' # domyslna definicja zmiennej
        dane_zrodlowe = self.dlg.mQgsFileWidget_dz.filePath()
        if dane_zrodlowe != '':
            plikZrodlowy = [dane_zrodlowe, self.dlg.mQgsFileWidget_dz.filter()]
            with open(str(pathlib.Path(plikZrodlowy[0])), 'rb') as f:
              while True:
                  data = f.read(65536)
                  if not data:
                      break
                  crc = binascii.crc32(data, crc)
            sumaKontrolnaDaneZrodlowe = "%08X" % (crc & 0xFFFFFFFF)
        
        if result and self.dlg.mComboBox.currentText() != '':
            nazwa = f"Walidacja pliku {str(pathlib.Path(plik[0]).name)} rozpoczęta ..."
            self.iface.messageBar().pushMessage(nazwa, level=Qgis.Info)
        
            timestr = time.strftime("%Y-%m-%d_%H.%M")
            base = f"RaportBledow_{nazwa_pliku}_{timestr}"
            pathPDF = os.path.join(sciezkaGML, f"{base}.pdf")
            pathXLS = os.path.join(sciezkaGML, f"{base}.xls")
            pathXLSX = os.path.join(sciezkaGML, f"{base}.xlsx")
            pathTXT = os.path.join(sciezkaGML, f"{base}.txt")
        else:
            return
        
        pliki = []
        plikiZparsowane = []
        walidowanePliki_df = []
        wiersze_df = []
        opisyBledow_df = []
        komunikatyBledow_df = []
        unikalny_id = set()
        kontrolowanePliki_df = []
        klasy_df = []
        gmlid_df_w = []
        gmlid_df_k = []
        komunikatyBledowKontroli_df = []
        grupyKontroli_df = []
        slownikBledow = {}
        slownikWalidacji = {}
        idkontroli_LiczbaBledow = {}
        if self.dlg.comboBox.currentText() == 'EGIB': # przenoszenie zmiennych do EGiB
            self.komunikatyBledowKontroli_df = komunikatyBledowKontroli_df
            self.kontrolowanePliki_df = kontrolowanePliki_df
            self.klasy_df = klasy_df
            self.gmlid_df_w = gmlid_df_w
            self.gmlid_df_k = gmlid_df_k
            self.grupyKontroli_df = grupyKontroli_df
            self.idkontroli_LiczbaBledow = idkontroli_LiczbaBledow
        kontrole_doInterpretacji = [
            'topo_e1_k2_3_1', 'topo_e1_k2_4_1', 'topo_e1_k2_8_1', 'topo_e1_k4_2', 'topo_e1_k20', 'topo_e1_k23', 'topo_e1_k23_4',
            'topo_e1_k123_7', 'topo_e1_k123_8', 'topo_e1_k123_9', 'topo_e1_k123_10',
            'topo_e3_k5', 'topo_e3_k7_1',
            'topo_e3_k159', 'topo_e3_k175', 'topo_e3_k176', 'topo_e3_k177', 'topo_e3_k178',
            'topo_e3_k179', 'topo_e3_k180', 'topo_e3_k181', 'topo_e3_k182', 'topo_e3_k183',
            'topo_e4_k25', 'topo_e4_k27',
            'topo_e5_k4'
        ]
        slownik_liczba_bledow = {}
        slownik_grupa = {}
        i = 0
        walidacjaZWynikiemPozytywnym = True
        kontrolaZWynikiemPozytywnym = True
        namespace = self.dlg.comboBox.currentText()
        try:
            with open(xsdPath, 'rb') as schema_file:
                schema_root = etree.XML(schema_file.read())
                xmlschema = XMLSchema(schema_root)
        except Exception as a:
            print(a)
            self.iface.messageBar().pushMessage(
                "Uwaga!",
                ("Występuje problem z pobraniem schematu ze strony http://schemas.opengis.net."
                "Proszę sprawdzić czy jest dostępu do internetu."),
                level = Qgis.Critical)
            return
        
        for file in files:
            if '.zip' in str(plik[0]):
                try:
                    plikGML = plikZIP.extract(file, sciezkaGML)
                except:
                    self.iface.messageBar().pushMessage(
                        "Uwaga!",
                        "Problem z rozpakowaniem pliku zip.",
                        level = Qgis.Critical
                    )
                    return
            else:
                plikGML = file
            if plikGML[-3:] in ['xml','gml','XML','GML'] and not plikGML.__contains__("Uzytkownik"):
                pliki.append(plikGML)
                if self.dlg.comboBox.currentText() != 'EGIB':
                    loop = QEventLoop()
        
        
                    def on_finished_wrapper(*args, **kwargs):
                        """Kończy pętlę oczekiwania po zakończeniu zadania walidacji."""
                        try:
                                self.wynikiWalidacji(*args, **kwargs)
                        finally:
                            loop.quit()
                    task = QgsTask.fromFunction(
                        str(i),
                        self.walidacja,
                        on_finished=on_finished_wrapper,
                        flags = QgsTask.CanCancel
                    )
                    QgsApplication.taskManager().addTask(task)
                    loop.exec()
                else:
                   exception = None
                   result = None
                   class _FakeTask:
                       def __init__(self, desc):
                           """Inicjalizuje obiekt i zapisuje ustawienia potrzebne do dalszej pracy."""
                           self._desc = str(desc)
        
        
                       def description(self):
                          """Zwraca opis zastępczego zadania używanego przy walidacji synchronicznej."""
                          return self._desc
                   fake_task = _FakeTask(i)
                   try:
                           result = self.walidacja(fake_task)
                   except Exception as e:
                       exception = e
                   self.wynikiWalidacji(exception, result)
                i += 1
        if not walidacjaZWynikiemPozytywnym:
            msgBox = QMessageBox()
            msgBox.setText(
                "W pliku gml występują błędy zgodności ze schematem aplikacyjnym"
                " i dlatego dalsze kontrole atrybutowe i geometryczne"
                " mogą się nie wykonać poprawnie - czy chcesz  kontynuować?"
            )
            msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            odp = msgBox.exec()
            nazwy = ''
        else:
            odp = 0
        
        liczbaKontroliDoWykonania = 0
        liczbaKontroliWykonanych = 0
        nazwy = []
        epsg = 'epsg:2180'
        
        QgsMessageLog.logMessage(
            f'--------- Kontrole atrybutów dla pliku:{str(pathlib.Path(plik[0]).name)}---------',
            tag="Walidator plików GML",
            level=Qgis.Info
            )
        
        # sprawdzenie liczby kontroli do wykonania
        for i in range(modelKontroli.rowCount()):
            parent = modelKontroli.item(i)
            if parent.checkState() in (1,2):
                for j in range(parent.rowCount()):
                    if parent.child(j).checkState() == 2:
                        if parent.child(j).data(3) == "OT":
                            liczbaKontroliDoWykonania += 69 # bez klas RT
                        else:
                            liczbaKontroliDoWykonania += 1
                        nazwy.append((parent.child(j).data(1),parent.child(j).data(2)))
        
        if not walidacjaZWynikiemPozytywnym or liczbaKontroliDoWykonania > 0:
            mainGroup = os.path.splitext(os.path.basename(plik[0]))[0]
            root = QgsProject.instance().layerTreeRoot()
            groupaGlowna = next(
                (group
                 for group in root.children()
                 if group.name() == mainGroup),
                None,
            )
            if groupaGlowna:
               # Jeśli grupa istnieje, sprawdzamy czy jest usunięta, jeśli tak - tworzymy nową
                   root.removeChildNode(groupaGlowna)
            groupaGlowna = root.addGroup(mainGroup)
            self.groupaGlowna = groupaGlowna
            if self.dlg.comboBox.currentText() == 'EGIB':
                self.groupaGlowna = groupaGlowna
            for plikZparsowany in plikiZparsowane:
                self.importGML(plikZparsowany)
        
        warstwyBledowKontroliAtrybutow = {
            'Point': QgsVectorLayer(f"Point?crs={epsg}", "błędy z kontroli atrybutów", "memory"),
            'LineString': QgsVectorLayer(f"LineString?crs={epsg}", "błędy z kontroli atrybutów", "memory"),
            'Polygon': QgsVectorLayer(f"Polygon?crs={epsg}", "błędy z kontroli atrybutów", "memory")
        }
        warstwyBledowWalidacji = {
            'Point': QgsVectorLayer(f"Point?crs={epsg}", "błędy z walidacji", "memory"),
            'LineString': QgsVectorLayer(f"LineString?crs={epsg}", "błędy z walidacji", "memory"),
            'Polygon': QgsVectorLayer(f"Polygon?crs={epsg}", "błędy z walidacji", "memory")
        }
        for layer in warstwyBledowKontroliAtrybutow.values():
            provider = layer.dataProvider()
            provider.addAttributes([
                QgsField('gml_id', QVariant.String),
                QgsField('nazwaKlasy', QVariant.String),
                QgsField('trescBledu', QVariant.String)
                ])
            layer.updateFields()
        for layer in warstwyBledowWalidacji.values():
            provider = layer.dataProvider()
            provider.addAttributes([
                QgsField('walidowanyPlik', QVariant.String),
                QgsField('wiersz', QVariant.String),
                QgsField('opisBledu', QVariant.String),
                QgsField('komunikatBledu', QVariant.String)
                ])
            layer.updateFields()
        
        if odp == 16384 or odp == 0:
            progress = QProgressBar()
        
            progressMessageBar = iface.messageBar().createMessage(
                "Postęp wykonania kontroli dodatkowych"
            )
            progress.setMaximum(liczbaKontroliDoWykonania)
            progress.setAlignment(Qt.AlignLeft|Qt.AlignVCenter)
            progressMessageBar.layout().addWidget(progress)
            iface.messageBar().pushWidget(progressMessageBar, Qgis.Info)
            self.kontrolaAtrybutow()
        
            if liczbaKontroliWykonanych != liczbaKontroliDoWykonania:
                progress.setValue(progress.maximum())
            iface.messageBar().clearWidgets()
            # statystyki
            listaBledow = {k: v for k, v in idkontroli_LiczbaBledow.items() if v > 0}
            if self.dlg.comboBox.currentText() == 'EGIB':
                self.listaBledow = listaBledow
            grupy = {}
            grupa_map = {}
            for i in range(modelKontroli.rowCount()):
                item = modelKontroli.item(i)
                grupa_nazwa = item.data(2)
                if item.rowCount() > 0:
                    for j in range(item.rowCount()):
                        child = item.child(j)
                        kontrola_id = child.data(1)
                        kontrola_name = child.data(2)
                        fraza = kontrola_id.split('_')[1][:2]
                        klucz_grupy = '_'.join(kontrola_id.split('_')[:2])
                        if fraza not in grupa_map:
                            grupa_map[fraza] = grupa_nazwa
                        if kontrola_id in idkontroli_LiczbaBledow:
                           wartosc = idkontroli_LiczbaBledow[kontrola_id]
                           if wartosc > 0:
                               slownik_liczba_bledow[kontrola_id] = (wartosc,
                                                                     kontrola_name
                                                                     )
            for klucz, wartosc in idkontroli_LiczbaBledow.items():
                fraza = klucz.split('_')[1][:2]
                klucz_grupy = '_'.join(klucz.split('_')[:2])
                if klucz_grupy in grupy:
                    grupy[klucz_grupy] += wartosc
                else:
                    grupy[klucz_grupy] = wartosc
            for klucz, suma in grupy.items():
                nazwa_grupy = grupa_map.get(klucz.split('_')[1][:2], "Brak nazwy")
                if suma > 0:
                    slownik_grupa[klucz] = (suma, nazwa_grupy)
            slownik_liczba_bledow = dict(sorted(slownik_liczba_bledow.items()))
            slownik_grupa = dict(sorted(slownik_grupa.items()))
        
        if not walidacjaZWynikiemPozytywnym:
            # Ustawienia paska postępu
            progressWalidacjaWyniki = QProgressBar()
            progressWalidacjaWynikiMessageBar = iface.messageBar().createMessage(
                "Postęp generowania warstw z błędami walidacji"
            )
            progressWalidacjaWyniki.setMaximum(len(groupaGlowna.findLayers()))
            progressWalidacjaWyniki.setAlignment(Qt.AlignLeft|Qt.AlignVCenter)
            progressWalidacjaWynikiMessageBar.layout().addWidget(progressWalidacjaWyniki)
            iface.messageBar().pushWidget(progressWalidacjaWynikiMessageBar, Qgis.Info)
        
            expression = '"gml_id" IN ({})'.format(",".join(f"'{gml_id}'" for gml_id in gmlid_df_w))
            request = QgsFeatureRequest().setFilterExpression(expression)
            warstwyZbledamiWalidacji = []
            for index, lyr in enumerate(groupaGlowna.findLayers()):
                lyr = lyr.layer()
                geometry_type = QgsWkbTypes.displayString(lyr.wkbType())
                new_features = [feature for feature in lyr.getFeatures(request)]
                if len(new_features) > 0:
                    new_layer = QgsVectorLayer(f"{geometry_type}?crs={lyr.crs().authid()}",
                                               f"{lyr.name()} błędy z walidacji",
                                               "memory"
                                )
                    new_layer_data_provider = new_layer.dataProvider()
                    new_layer_data_provider.addAttributes(
                        [QgsField("gml_id", QVariant.String),
                         QgsField("wiersz", QVariant.String),
                         QgsField("opisBledu", QVariant.String),
                         QgsField("komunikatBledu", QVariant.String)]
                    )
                    new_layer.updateFields()
                    for feature in new_features:
                        new_feature = QgsFeature(feature)
                        new_feature.setFields(new_layer.fields())
                        gmlid_df_w_idx = gmlid_df_w.index(feature['gml_id'])
                        new_feature.setAttribute(0, QVariant(feature['gml_id']))
                        new_feature.setAttribute(1, QVariant(wiersze_df[gmlid_df_w_idx]))
                        new_feature.setAttribute(2, QVariant(opisyBledow_df[gmlid_df_w_idx]))
                        new_feature.setAttribute(3, QVariant(komunikatyBledow_df[gmlid_df_w_idx]))
                        new_layer_data_provider.addFeature(new_feature)
        
                    QgsProject.instance().addMapLayer(new_layer)
                    warstwyZbledamiWalidacji.append(new_layer)
                progressWalidacjaWyniki.setValue(index)
            progressWalidacjaWyniki.reset()
        kontrolaZWynikiemPozytywnym = True
        if listaBledow:
             for kontrola_id in listaBledow:
                 if kontrola_id not in kontrole_doInterpretacji:
                     kontrolaZWynikiemPozytywnym = False
                     break
        else:
            kontrolaZWynikiemPozytywnym = True
        if self.dlg.comboBox.currentText() == 'EGIB':
            self.kontrolaZWynikiemPozytywnym = kontrolaZWynikiemPozytywnym
        
        if walidacjaZWynikiemPozytywnym and kontrolaZWynikiemPozytywnym:
            widget = iface.messageBar().createMessage(
                f"Walidacja pliku {str(pathlib.Path(plik[0]).name)} "
                "zakończona z wynikiem pozytywnym."
            )
            button = QPushButton(widget)
            button.setText("Pozytywny raport z kontroli / walidacji")
            button.pressed.connect(self.otwarcieRaportuZWalidacji)
            widget.layout().addWidget(button)
            self.iface.messageBar().pushWidget(widget,Qgis.Success)
        else:
            if walidacjaZWynikiemPozytywnym == False and kontrolaZWynikiemPozytywnym == False:
                msg = "Walidacja i kontrola atrybutów zakończona z wynikiem negatywnym: "
            elif walidacjaZWynikiemPozytywnym == False:
                msg = "Walidacja pliku zakończona z wynikiem negatywnym: "
            else:
                msg = "Kontrola atrybutów zakończona z wynikiem negatywnym: "
            widget = iface.messageBar().createMessage(msg, str(pathlib.Path(plik[0]).name))
        
            button = QPushButton(widget)
            button.setText("Raporty z kontroli / walidacji")
            button.pressed.connect(self.otwarcieRaportuZWalidacji)
            widget.layout().addWidget(button)
            self.iface.messageBar().pushWidget(widget, Qgis.Warning)
        
        if 'txt' in self.dlg.mComboBox.checkedItems():
           try:
               plikRaportu = open(pathTXT, "w")
               if len(walidowanePliki_df) > 0:
                   for i in range(len(walidowanePliki_df)):
                       plikRaportu.write(f'Walidacja pliku: {walidowanePliki_df[i]} z wynikiem negatywnym.\n')
                       plikRaportu.write(f'- {opisyBledow_df[i]}\n')
                       plikRaportu.write (f'- wiersz: {wiersze_df[i]}\n')
                       plikRaportu.write(f'- komunikat błędu: {komunikatyBledow_df[i]}\n')
               else:
                   plikRaportu.write('------------------------------------------------Brak błędów walidacji-----------------------------------------------------\n')
               if len(kontrolowanePliki_df) > 0:
                   plikRaportu.write('------------------------------------------------ KONTROLE ATRYBUTÓW ------------------------------------------------\n')
                   for i in range(len(kontrolowanePliki_df)):
                       opisBledu = (
                           f'KONTROLOWANIE PLIKI: {kontrolowanePliki_df[i]}'
                           f'KLASA: {klasy_df[i]}'
                           f'GMLID: {gmlid_df_k[i]}'
                           f'KOMUNIKAT BŁĘDU: {komunikatyBledowKontroli_df[i]}\n'
                       )
                       plikRaportu.write(opisBledu)
               else:
                   plikRaportu.write('------------------------------------------------ Brak błędów atrybutowych ------------------------------------------------\n')
               plikRaportu.close()
           except:
               self.iface.messageBar().pushMessage(
                   "Uwaga!", "Brak uprawnień do zapisu raportu.",
                   level=Qgis.Critical
              )
        
        if 'pdf' in self.dlg.mComboBox.checkedItems():
             from .fpdf import FPDF # tutaj importy by nie przeciążyły pamięci
             from .fpdf import FontFace
             from .fpdf.enums import XPos, YPos
             nazwaPliku = str(pathlib.Path(plik[0]).name)
             bledyWalidacji = {
                 'WALIDOWANY PLIK': walidowanePliki_df,
                 'WIERSZ': wiersze_df,
                 'OPIS BŁĘDU': opisyBledow_df,
                 'KOMUNIKAT BŁĘDU': komunikatyBledow_df,
                 'GMLID':gmlid_df_w
                 }
             bledyKontroli = {
                 'KLASA':klasy_df,
                 'GMLID':gmlid_df_k,
                 'KOMUNIKAT BŁĘDU':komunikatyBledowKontroli_df
                  }
             dfWalidacji = pd.DataFrame(bledyWalidacji)
             dfKontroli = pd.DataFrame(bledyKontroli)
             datawalidacja = dfWalidacji.values.tolist()
             datakontrola = dfKontroli.values.tolist()
             grupy_str = {
                 klucz: (wartosc[1], str(wartosc[0]))
                 for klucz, wartosc in slownik_grupa.items()
             } # Zamiana wartości w słowniku liczba błędów na stringi, choć powinno być ze slownik_rupa
             slownik_liczba_bledow_str = {
                 klucz: (wartosc[1],
                         str(wartosc[0]))
                         for klucz, wartosc in slownik_liczba_bledow.items()
             }
             lista_grup = [
                 [klucz, wartosc[0], wartosc[1]]
                 for klucz, wartosc in grupy_str.items()
                 ]
             lista_bledow = [
                 [klucz, wartosc[0],wartosc[1]]
                 for klucz, wartosc in slownik_liczba_bledow_str.items()
                 ]
             # Łączenie obu list
             dataSlownik = lista_grup + lista_bledow
             if len(str(bledyWalidacji["WIERSZ"])) == 0:
                 bledyWalidacji["WIERSZ"] = 0
             # tworzenie dokumentu
             class PDF(FPDF): # stopka z numerem strony
                 def header(self):
                     """Dodaje nagłówek do generowanego raportu PDF."""
                     if 'verdana' not in self.fonts:
                         self.add_font(
                             'verdana',
                             '',
                             os.path.join(
                                 os.path.dirname(os.path.realpath(__file__)),
                                  "fpdf",
                                  "verdana.ttf"
                             ),
                           )
                     self.set_font('verdana', size = 5)
                     self.cell(0, -5,
                               (
                                   'Raport został wygenerowany przy pomocy wtyczki QGIS'
                                   f' – „Walidator plików GML” w wersji {version} - udostępnionej przez GUGiK'
                                ),
                               new_x=XPos.RIGHT,
                               new_y=YPos.TOP,
                               align="L"
                    )
        
        
                 def footer(self):
                     """Dodaje stopkę do generowanego raportu PDF."""
                     self.set_y(-15)
                     self.cell(
                         0,
                         10,
                         f'Strona {self.page_no()}',
                         new_x=XPos.RIGHT,
                         new_y=YPos.TOP,
                         align='C'
                    )
        
        
             def create_pdf(filename):
                 """Tworzy raport PDF z wynikami walidacji i kontroli."""
                 czas = time.strftime("%Y-%m-%d %H:%M")
                 pdf = PDF()
                 walidator_sciezka = os.path.dirname(os.path.realpath(__file__))
                 pdf.add_page()
                 pdf.alias_nb_pages()
                 pdf.set_margins(10,10,10)
                 if 'verdana' not in pdf.fonts:
                     pdf.add_font(
                         'verdana',
                         '',
                         os.path.join(
                             walidator_sciezka,
                             "fpdf",
                             "verdana.ttf"
                         ),
                    )
                 pdf.add_font(
                     "verdana",
                     "B",
                     os.path.join(
                         walidator_sciezka,
                         "fpdf",
                         "verdana-bold.ttf"
                         ),
                     )
                 pdf.set_font('verdana', 'B',14)
                 pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(0, 0, "Raport z kontroli", align='C', new_x=XPos.LEFT, new_y=YPos.NEXT)
                 pdf.set_font("verdana", "", 8)
                 pdf.cell(0,10, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(100,0, "Wskazany plik:", align='R', new_x=XPos.RIGHT, new_y=YPos.TOP)
                 pdf.cell(100,0, nazwaPliku, align = "L")
                 pdf.cell(0,6, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.set_font("verdana", "", 10)
                 pdf.cell(100,0, "Wynik kontroli:", align='R', new_x=XPos.RIGHT, new_y=YPos.TOP)
                 if self.dlg.comboBox.currentText() == 'EGIB':
                     liczbaKontroli = self.liczbaKontroliWykonanych
                 else:
                     liczbaKontroli = liczbaKontroliWykonanych
                 wynikFinalny  = "Pozytywny" if (walidacjaZWynikiemPozytywnym and kontrolaZWynikiemPozytywnym) else "Negatywny"
                 wynikWalidacja  = "Pozytywny" if walidacjaZWynikiemPozytywnym else "Negatywny"
                 wynikKontrola  = "Pozytywny" if kontrolaZWynikiemPozytywnym else "Negatywny"
                 if liczbaKontroli != 0:
                     # były zaznaczone kontrole
                     wynikKontroli   = wynikFinalny # globalny finalny wynik
                     wynikWalidacji  = wynikWalidacja
                     wynikKonDodat   = wynikKontrola
                 else:
                     # użytkownik chciał tylko walidację, bez kontroli
                     wynikKonDodat   = "Nie dotyczy"
                     wynikWalidacji  = wynikWalidacja
                     wynikKontroli   = "Pozytywny" if walidacjaZWynikiemPozytywnym else "Negatywny"
                 # Ustaw kolor tekstu w zależności od wyniku
                 if wynikKontroli == "Pozytywny":
                     pdf.set_text_color(0, 153, 0)
                 else:
                     pdf.set_text_color(255, 0, 0)
                 pdf.cell(100,0, wynikKontroli, align = "L")
                 pdf.cell(0,6, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.set_text_color(0,0,0)
                 pdf.set_font("verdana", "", 8)
                 pdf.cell(100,0, "Wynik walidacji:", align="R", new_x=XPos.RIGHT, new_y=YPos.TOP)
                 if wynikWalidacji == "Pozytywny":
                    pdf.set_text_color(0,153,0)
                 else:
                    pdf.set_text_color(255,0,0)
                 lightblue = (143, 216, 255)
                 gray = (240,240,240)
                 pdf.cell(100,0,wynikWalidacji, align = "L")
                 pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.set_text_color(0,0,0)
                 pdf.cell(100,0, "Wynik kontroli dodatkowych:", align="R", new_x=XPos.RIGHT, new_y=YPos.TOP)
                 if wynikKonDodat == "Pozytywny":
                         pdf.set_text_color(0,153,0)
                 elif wynikKonDodat == "Negatywny":
                         pdf.set_text_color(255,0,0)
                 else:
                     # przypadek "Nie dotyczy"
                     pdf.set_text_color(0,0,0)
                 pdf.cell(100,0, wynikKonDodat, align = "L")
                 pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.set_text_color(0,0,0)
                 pdf.cell(100, 0, "Data kontroli:", align="R", new_x=XPos.RIGHT, new_y=YPos.TOP)
                 pdf.cell(100, 0, czas, align="L", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(100,0,"Wersja szablonu:", align = "R")
                 pdf.cell(100,0,wersjaSzablonuKontroli, align = "L", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(0,1, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 with pdf.table(
                         first_row_as_headings=False,
                         col_widths=(105,95),
                         borders_layout="NONE",
                         v_align="TOP"
                     ) as tabelaskp:
                     rzad = tabelaskp.row()
                     rzad.cell("Szablon kontroli:", align="R", padding=(1,1,1,1))
                     rzad.cell(szablonKontroliPath, align="J", padding=(1,5,1,1))
                 pdf.cell(0,1, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(100,0,"Wersja schematu aplikacyjnego GML:", align="R", new_x=XPos.RIGHT, new_y=YPos.TOP)
                 pdf.cell(100,0, wersjaSchematu, align = "L", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(0,1, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 with pdf.table(
                         first_row_as_headings=False,
                         col_widths=(105,95),
                         borders_layout="NONE",
                         v_align="TOP"
                     ) as tabelaskp:
                     rzad = tabelaskp.row()
                     rzad.cell("Schemat aplikacyjny GML:", align="R", padding = (1,1,1,1))
                     rzad.cell(xsdPath, align = "J", padding = (1,5,1,1))
                 pdf.cell(0,1, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(100,0,"Suma kontrolna szablonu kontroli (CRC32):", align="R", new_x=XPos.RIGHT, new_y=YPos.TOP)
                 pdf.cell(100,0, sumaKontrolaSzablonKontroli, align = "L", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(100,0,"Suma kontrolna schematu (CRC32):", align="R", new_x=XPos.RIGHT, new_y=YPos.TOP)
                 pdf.cell(100,0, sumaKontrolaSchemat, align = "L", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.cell(100,0,"Suma kontrolna wskazanego pliku (CRC32):", align="R", new_x=XPos.RIGHT, new_y=YPos.TOP)
                 pdf.cell(100,0,sumaKontrolaPlikWalidowany, align = "L")
                 pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 if sumaKontrolnaDaneZrodlowe != "nie dotyczy":
                      pdf.cell(100,0,"Suma kontrola danych wydanych (CRC32):", align="R", new_x=XPos.RIGHT, new_y=YPos.TOP)
                      pdf.cell(100,0, sumaKontrolnaDaneZrodlowe, align = "L")
                      pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 if len(nazwy) > 0:
                      pdf.set_font("verdana", "B", 10)
                      pdf.cell(0,0,"Tabela ze zleconymi kontrolami dodatkowymi", align = "C")
                      pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                      pdf.set_font("verdana", "", 8)
                      pdf.set_fill_color(lightblue)
                      with pdf.table(col_widths=(12,40),
                                     text_align = "C",
                                     padding = (1)) as table:
                          pdf.set_font("verdana", "", 5)
                          headings = table.row()
                          headings.cell("ID KONTROLI")
                          headings.cell("ZAKRES KONTROLI")
                          pdf.set_fill_color(gray)
                      with pdf.table(nazwy, col_widths=(12,40),
                                     first_row_as_headings=False,
                                     text_align="L",
                                     padding = (1)
                                     ):
                          pass
                      pdf.cell(0,12, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 pdf.set_fill_color(255,255,255)
                 pdf.set_font("verdana", "" , 6)
                 # tabela statystyk
                 if len(dataSlownik)> 0:
                     pdf.set_font("verdana", "B", 10)
                     pdf.cell(0,0,"Statystyki Kontroli", align = "C")
                     pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                     pdf.set_font("verdana", "", 8)
                     pdf.set_fill_color(lightblue)
                     with pdf.table(
                             col_widths=(16,80,12),
                             text_align="C",
                             padding=(1)
                             ) as table:
                         pdf.set_font("verdana", "", 5)
                         headings = table.row()
                         headings.cell("IDENTYFIKATOR KONTROLI")
                         headings.cell("NAZWA KONTROLI")
                         headings.cell("LICZBA BŁĘDÓW")
                         pdf.set_fill_color(gray)
                     with pdf.table(
                             dataSlownik,
                             col_widths=(16,80,12),
                             first_row_as_headings=False,
                             text_align="L",
                             padding = (1)):
                          pass
                     pdf.cell(0,12, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 # tabela walidacji
                 if len(bledyWalidacji["WIERSZ"]) > 0:
                     pdf.set_font("verdana", "B", 10)
                     pdf.cell(0,0,"Tabela z błędami walidacji", align = "C")
                     pdf.set_font("verdana", "", 8)
                     pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                     pdf.set_fill_color(lightblue)
                     with pdf.table(
                             col_widths=(40,12,35,68,50),
                             text_align="C",
                             padding=(1)
                             ) as table:
                         pdf.set_font("verdana", "", 5)
                         headings = table.row()
                         headings.cell("WALIDOWANY PLIK")
                         headings.cell("WIERSZ")
                         headings.cell("OPIS BŁĘDU")
                         headings.cell("KOMUNIKAT BŁĘDU")
                         headings.cell("GMLID")
                         pdf.set_fill_color(gray)
                     with pdf.table(
                             datawalidacja,
                             col_widths=(40,12,35,68,50),
                             first_row_as_headings=False,
                             text_align="L",
                             padding=(1)
                             ):
                         pass
                     pdf.set_fill_color(255,255,255)
                     pdf.set_font("verdana", "" , 6)
                 else:
                     pdf.set_font("verdana", "B", 10)
                     pdf.cell(0, 0, "Brak błędów walidacji", align = "C")
                 pdf.cell(w=0, h=10, text=" ", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                 # tabela konotroli
                 if len(bledyKontroli["KLASA"]) > 0:
                     pdf.set_font("verdana", "B", 10)
                     naglowek = [["KLASA", "GMLID", "KOMUNIKAT BŁĘDU"]] # Definicja nagłówka
                     datakontrola2 = naglowek + datakontrola # dodanie do tablicy
                     pdf.set_font("verdana", "B", 10)
                     pdf.cell(0,0,"Tabela z błędami kontroli dodatkowych", align = "C")
                     pdf.set_font("verdana", "", 5)
                     pdf.cell(0,5, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
                     headings_style = FontFace(emphasis="BOLD", color=0, fill_color=lightblue)
                     with pdf.table(
                             datakontrola2,
                             num_heading_rows=1,
                             col_widths=(40,50,70),
                             cell_fill_color=(240,240,240),
                             cell_fill_mode="ROWS",
                             headings_style=headings_style,
                             first_row_as_headings=True,
                             text_align="C",
                             padding=(1)
                             ):
                         pass
                 elif liczbaKontroliWykonanych != 0:
                      pdf.set_font("verdana", "B", 10)
                      pdf.cell(0,0,"Brak błędów kontroli dodatkowych", align="C")
                      pdf.set_font("verdana", "", 5)
                 pdf.output(filename)
             create_pdf(pathPDF)
             # koniec tworzenia pdf
        
        if 'xls' in self.dlg.mComboBox.checkedItems():
            progressMessageBar = iface.messageBar().createMessage("Generowanie raportu xls...")
            bledyWalidacji = {
                'WALIDOWANY PLIK': walidowanePliki_df,
                'GMLID': gmlid_df_w,
                'WIERSZ': wiersze_df,
                'OPIS BŁĘDU': opisyBledow_df,
                'KOMUNIKAT BŁĘDU': komunikatyBledow_df
                }
            bledyKontroli = {
                'KLASA':klasy_df,
                'GMLID':gmlid_df_k,
                'KOMUNIKAT BŁĘDU':komunikatyBledowKontroli_df,
                'GRUPA KONTROLI': grupyKontroli_df
                }
            if len(bledyWalidacji["WIERSZ"]) == 0:
                bledyWalidacji["WIERSZ"] = 0
            dataFrameWalidacja = pd.DataFrame(bledyWalidacji)
            dataFrameKontrola = pd.DataFrame(bledyKontroli)
        
            try:
                strony = xlwt.Workbook()
                arkuszDaneWstepne = strony.add_sheet('Podsumowanie kontroli')
                arkuszStatystyk = strony.add_sheet('Statystyki')
                arkuszWalidacji = strony.add_sheet('Raport z walidacji')
                arkuszKontroli = strony.add_sheet('Raport z kontroli dodatkowych')
                styl1 = xlwt.easyxf('align: wrap on, vert center;  borders: left thin, right thin, top thin, bottom thin')
                styl2 = xlwt.easyxf("align: horz center, vert center; font: bold on; align: wrap on; borders: left thin, right thin, top thin, bottom thin")
                styl3 = xlwt.easyxf("align: horz left, vert center; font: bold on; align: wrap on; borders: left thin, right thin, top thin, bottom thin")
                neg = xlwt.easyxf("align: horz left, vert center; font: bold on, color red; align: wrap on; borders: left thin, right thin, top thin, bottom thin")
                poz = xlwt.easyxf("align: horz left, vert center; font: bold on, color green; align: wrap on; borders: left thin, right thin, top thin, bottom thin")
                czr = xlwt.easyxf("align: horz left, vert center; font: bold on, color black; align: wrap on; borders: left thin, right thin, top thin, bottom thin")
                arkuszDaneWstepne.write(0,0,"Data kontroli i wskazany plik", styl3)
                arkuszDaneWstepne.write(1,0,"Wersja wtyczki", styl3)
                arkuszDaneWstepne.write(2,0,"Suma kontrolna szablonu kontroli (CRC32)", styl3)
                arkuszDaneWstepne.write(3,0,"Wersja szablonu kontroli", styl3)
                arkuszDaneWstepne.write(4,0,"Suma kontrolna schematu (CRC32)", styl3)
                arkuszDaneWstepne.write(5,0,"Wersja schematu aplikacyjnego GML", styl3)
                arkuszDaneWstepne.write(6,0,"Suma kontrolna wskazanego pliku (CRC32)", styl3)
                wiersz = 7
                if sumaKontrolnaDaneZrodlowe != 'nie dotyczy':
                    arkuszDaneWstepne.write(wiersz,0,"Suma kontrola danych wydanych (CRC32)", styl3)
                    arkuszDaneWstepne.write(wiersz,1,sumaKontrolnaDaneZrodlowe, styl1)
                    arkuszDaneWstepne.write(wiersz,2,'', styl1)
                    wiersz += 1
                arkuszDaneWstepne.write(wiersz,0,"Wynik kontroli", styl3)
                arkuszDaneWstepne.write(wiersz + 1,0,"Wynik walidacji", styl3)
                arkuszDaneWstepne.write(wiersz + 2,0,"Wynik kontroli dodatkowych", styl3)
                arkuszDaneWstepne.col(0).width = 10000
                arkuszDaneWstepne.col(1).width = 5000
                arkuszDaneWstepne.col(2).width = 35000
                arkuszDaneWstepne.write(0,1,time.strftime("%Y-%m-%d %H:%M"), styl1)
                arkuszDaneWstepne.write(0,2,str(pathlib.Path(plik[0]).name), styl1)
                arkuszDaneWstepne.write(1,1,str(version),styl1)
                arkuszDaneWstepne.write(1,2,'', styl1)
                arkuszDaneWstepne.write(2,1,sumaKontrolaSzablonKontroli, styl1)
                arkuszDaneWstepne.write(2,2,'', styl1)
                arkuszDaneWstepne.write(3,1,wersjaSzablonuKontroli, styl1)
                arkuszDaneWstepne.write(3,2,szablonKontroliPath, styl1)
                arkuszDaneWstepne.write(4,1,sumaKontrolaSchemat, styl1)
                arkuszDaneWstepne.write(4,2,'', styl1)
                arkuszDaneWstepne.write(5,1,wersjaSchematu, styl1)
                arkuszDaneWstepne.write(5,2,xsdPath, styl1)
                arkuszDaneWstepne.write(6,1,sumaKontrolaPlikWalidowany, styl1)
                arkuszDaneWstepne.write(6,2,'', styl1)
                arkuszDaneWstepne.write(wiersz,2,'', styl1)
                arkuszDaneWstepne.write(wiersz + 1,2,'', styl1)
                arkuszDaneWstepne.write(wiersz + 2,2,'', styl1)
                for i, (data1, data2) in enumerate(nazwy):
                    wierszTabela = i + wiersz + 3
                    if wierszTabela >= 65536:  # 0..65535
                        QMessageBox.warning(
                        QMessageBox(),
                        'Ograniczenie xls',
                        'Ograniczono listę zleconych kontroli do 65 536 wierszy.',
                        QMessageBox.Ok
                        )
                        break
                    arkuszDaneWstepne.write(wierszTabela,0,"Zlecona kontrola dodatkowa", styl3)
                    arkuszDaneWstepne.write(wierszTabela,1, f"{data1}", styl1)
                    arkuszDaneWstepne.write(wierszTabela,2, f"{data2}", styl1)
                arkuszStatystyk.write(0,0,"IDENTYFIKATOR KONTROLI", styl2)
                arkuszStatystyk.write(0,1,"NAZWA KONTROLI", styl2)
                arkuszStatystyk.write(0,2,"LICZBA BŁĘDÓW", styl2)
                arkuszStatystyk.col(0).width = 5000
                arkuszStatystyk.col(1).width = 15000
                arkuszStatystyk.col(2).width = 4000
                start_row = len(slownik_grupa) + 2
                for i, (k, (w, n)) in enumerate(slownik_grupa.items()):
                    arkuszStatystyk.write(i+1,0, k, styl1)
                    arkuszStatystyk.write(i+1,1, n, styl1)
                    arkuszStatystyk.write(i+1,2, w, styl1)
                for j,(klucz, (wartosc, nazwa)) in enumerate(slownik_liczba_bledow.items()):
                    arkuszStatystyk.write(j+start_row,0, klucz, styl1)
                    arkuszStatystyk.write(j+start_row,1, nazwa, styl1)
                    arkuszStatystyk.write(j+start_row,2, wartosc, styl1)
                arkuszWalidacji.write(0,0,"WALIDOWANY PLIK", styl2)
                arkuszWalidacji.write(0,1,"GMLID", styl2)
                arkuszWalidacji.write(0,2,"WIERSZ", styl2)
                arkuszWalidacji.write(0,3,"OPIS BŁĘDU", styl2)
                arkuszWalidacji.write(0,4,"KOMUNIKAT BŁĘDU", styl2)
                arkuszWalidacji.col(0).width = 12500
                arkuszWalidacji.col(1).width = 11000
                arkuszWalidacji.col(2).width = 2000
                arkuszWalidacji.col(3).width = 10000
                arkuszWalidacji.col(4).width = 10000
                arkuszKontroli.write(0,0,"KLASA", styl2)
                arkuszKontroli.write(0,1,"GMLID", styl2)
                arkuszKontroli.write(0,2,"KOMUNIKAT BŁĘDU", styl2)
                arkuszKontroli.write(0,3,"GRUPA KONTROLI", styl2)
                arkuszKontroli.col(0).width = 4000
                arkuszKontroli.col(1).width = 12500
                arkuszKontroli.col(2).width = 27500
                arkuszKontroli.col(3).width = 10000
                for i, row in enumerate(dataFrameWalidacja.values):
                    if i > 65500:
                        QMessageBox.critical(
                            QMessageBox(),
                            'Ograniczenie xls',
                            'Liczba wierszy xls została ograniczona do 65535 błędów.',
                            QMessageBox.Ok
                            )
                        break
                    else:
                        for j, val in enumerate(row):
                            arkuszWalidacji.write(i + 1, j, str(val), styl1)
        
                for i, row in enumerate(dataFrameKontrola.values):
                    if i > 65500:
                        QMessageBox.critical(
                            QMessageBox(),
                            'Ograniczenie xls',
                            'Liczba wierszy xls została ograniczona do 65535 błędów.',
                            QMessageBox.Ok
                            )
                        break
                    else:
                        for j, val in enumerate(row):
                            arkuszKontroli.write(i + 1, j, str(val), styl1)
                if self.dlg.comboBox.currentText() == 'EGIB':
                    liczbaKontroliWykonanych = self.liczbaKontroliWykonanych
                wynik_5_6_7 = "Pozytywny" if walidacjaZWynikiemPozytywnym and kontrolaZWynikiemPozytywnym else "Negatywny"
                wynik_6 = "Pozytywny" if walidacjaZWynikiemPozytywnym else "Negatywny"
                wynik_7 = "Pozytywny" if kontrolaZWynikiemPozytywnym else "Negatywny"
                if liczbaKontroliWykonanych != 0:
                    arkuszDaneWstepne.write(wiersz, 1, wynik_5_6_7, poz if wynik_5_6_7 == "Pozytywny" else neg)
                    arkuszDaneWstepne.write(wiersz + 1, 1, wynik_6, poz if wynik_6 == "Pozytywny" else neg)
                    arkuszDaneWstepne.write(wiersz + 2, 1, wynik_7, poz if wynik_7 == "Pozytywny" else neg)
                else:
                    arkuszDaneWstepne.write(wiersz + 2, 1, "Nie dotyczy", czr)
                    if walidacjaZWynikiemPozytywnym and kontrolaZWynikiemPozytywnym:
                        arkuszDaneWstepne.write(wiersz, 1, "Pozytywny", poz)
                        arkuszDaneWstepne.write(wiersz + 1, 1, "Pozytywny", poz)
                    else:
                        arkuszDaneWstepne.write(wiersz, 1, "Negatywny", neg)
                        arkuszDaneWstepne.write(
                            wiersz + 1,
                            1,
                            "Negatywny" if not walidacjaZWynikiemPozytywnym else "Pozytywny",
                            neg if not walidacjaZWynikiemPozytywnym else poz
                        )
                strony.save(pathXLS)
            finally:
                try:
                    # wyczyść referencje, żeby obiekty xlwt wyszły z zasięgu
                    arkuszDaneWstepne = arkuszStatystyk = arkuszWalidacji = arkuszKontroli = None
                    styl1 = styl2 = styl3 = neg = poz = czr = None
                    strony = None
                except Exception as e:
                    print(f'Błąd w tworzeniu pliku xls: {e}')
                    return
            # koniec tworzenia xls
        
        if 'xlsx' in self.dlg.mComboBox.checkedItems(): # nowoczesniejsza wersja xlsx
            progressMessageBar = iface.messageBar().createMessage("Generowanie raportu xlsx...")
            bledyWalidacji = {
                'WALIDOWANY PLIK': walidowanePliki_df,
                'GMLID': gmlid_df_w,
                'WIERSZ': wiersze_df,
                'OPIS BŁĘDU': opisyBledow_df,
                'KOMUNIKAT BŁĘDU': komunikatyBledow_df
                }
            bledyKontroli = {
                'KLASA':klasy_df,
                'GMLID':gmlid_df_k,
                'KOMUNIKAT BŁĘDU':komunikatyBledowKontroli_df,
                'GRUPA KONTROLI': grupyKontroli_df
                }
            try:
                idenkontroli = []
                nazwKontroli = []
                liczBledy = []
                for i, (k, (w, n)) in enumerate(slownik_grupa.items()):
                    idenkontroli.append(k)
                    nazwKontroli.append(n)
                    liczBledy.append(w)
                if len(slownik_grupa) > 0:
                    idenkontroli.append("") # dodanie pustego wiersza w przerwie
                    nazwKontroli.append("")
                    liczBledy.append("")
                for j,(klucz, (wartosc, nazwa)) in enumerate(slownik_liczba_bledow.items()):
                    idenkontroli.append(klucz)
                    nazwKontroli.append(nazwa)
                    liczBledy.append(wartosc)
                daneStatystyk = {
                    'IDENTYFIKATOR KONTROLI':idenkontroli,
                    'NAZWA KONTROLI':nazwKontroli,
                    'LICZBA BŁĘDÓW': liczBledy
                    }
                if self.dlg.comboBox.currentText() == 'EGIB':
                    liczbaKontroliWykonanych = self.liczbaKontroliWykonanych
                wynik_5_6_7 = "Pozytywny" if walidacjaZWynikiemPozytywnym and kontrolaZWynikiemPozytywnym else "Negatywny"
                wynik_6 = "Pozytywny" if walidacjaZWynikiemPozytywnym else "Negatywny"
                wynik_7 = "Pozytywny" if kontrolaZWynikiemPozytywnym else "Negatywny"
                if liczbaKontroliWykonanych != 0:
                  wynikKontroli = wynik_5_6_7
                  wynikWalidacji =  wynik_6
                  wynikKonDodat = wynik_7
                else:
                    wynikKonDodat = 'Nie dotyczy'
                    wynikWalidacji =  wynik_6
                    if walidacjaZWynikiemPozytywnym:
                        wynikKontroli = "Pozytywny"
                    else:
                        wynikKontroli = "Negatywny"
                if len(bledyWalidacji["WIERSZ"]) == 0:
                    bledyWalidacji["WIERSZ"] = 0
        
                daneWstepne = [
                    ["Data kontroli i wskazany plik",time.strftime("%Y-%m-%d %H:%M"),
                     str(pathlib.Path(plik[0]).name)],
                    ["Wersja wtyczki",str(version), ''],
                    ["Suma kontrolna szablonu kontroli (CRC32)",sumaKontrolaSzablonKontroli, ''],
                    ["Wersja szablonu kontroli",wersjaSzablonuKontroli, szablonKontroliPath],
                    ["Suma kontrolna schematu (CRC32)",sumaKontrolaSchemat, ''],
                    ["Wersja schematu aplikacyjnego GML",wersjaSchematu, xsdPath],
                    ["Suma kontrolna wskazanego pliku (CRC32)", sumaKontrolaPlikWalidowany, ''],
                    ["Wynik kontroli",wynikKontroli,''],
                    ["Wynik walidacji",wynikWalidacji,''],
                    ["Wynik kontroli dodatkowych",wynikKonDodat,'']]
                if sumaKontrolnaDaneZrodlowe != 'nie dotyczy':
                    daneWstepne.insert(7, ["Suma kontrola danych wydanych (CRC32)", sumaKontrolnaDaneZrodlowe, ''])
                for i, (data1, data2) in enumerate(nazwy):
                    daneWstepne.append(
                        ["Zlecona kontrola dodatkowa", data1, data2]
                    )
                arkuszDaneWstepne = pd.DataFrame(daneWstepne)
                arkuszStatystyk = pd.DataFrame(daneStatystyk)
                dataFrameWalidacja = pd.DataFrame(bledyWalidacji)
                dataFrameKontrola = pd.DataFrame(bledyKontroli)
                czcionka = Font(name='Arial', size=10)
                czcioNagl = Font(name='Arial', size=10, bold= True)
                thin_border = Border( # utworzenie ramki
                    left=Side(style="thin"),
                    right=Side(style="thin"),
                    top=Side(style="thin"),
                    bottom=Side(style="thin")
                    )
                alignment = Alignment(wrap_text=True)
                alignment_center = Alignment(
                    horizontal='center',
                    vertical='center',
                    wrap_text=True
                ) # nagłówki na srodku
                with pd.ExcelWriter(pathXLSX) as writer:
                    arkuszDaneWstepne.to_excel(
                        writer,
                        sheet_name="Podsumowanie kontroli",
                        index=False,
                        header=False
                    )
                    worksheet = writer.sheets["Podsumowanie kontroli"]
                    max_col = worksheet.max_column
                    max_row = worksheet.max_row
                    worksheet.column_dimensions["A"].width = 41 # 1 arkusz
                    worksheet.column_dimensions["B"].width = 20
                    worksheet.column_dimensions["C"].width = 136
                    for row in range(1, worksheet.max_row + 1):  # zmiana szerokosci wierszy
                        worksheet.row_dimensions[row].height = 12.75
                    # Formatowanie pierwszej kolumny jako pogrubionej
                    for row in worksheet.iter_rows(
                            min_col=1,
                            max_col=1,
                            min_row=1,
                            max_row=max_row
                        ):
                        for cell in row:
                            cell.font = czcioNagl
                            cell.border = thin_border
                            cell.alignment = alignment
                    for row in worksheet.iter_rows(
                            min_col=2,
                            max_col=max_col,
                            min_row=1,
                            max_row=max_row
                        ): # ustawiania na pozostałcy kolumnach
                        for cell in row:
                            cell.font = czcionka
                            cell.border = thin_border
                            cell.alignment = alignment
                            if cell.value == "Pozytywny": # kolorowanie wyników kontroli
                                cell.font = Font(bold=True, color="008000")  # Zielony
                            elif cell.value == "Negatywny":
                                cell.font = Font(bold=True, color="FF0000")  # Czerwony
                            elif cell.value == "Nie dotyczy":
                                cell.font = Font(bold=True)  # Domyślny czarny
        
                    arkuszStatystyk.to_excel(
                        writer,
                        sheet_name="Statystyki",
                        index=False
                    )
                    worksheet = writer.sheets["Statystyki"] # 2 arkusz
                    worksheet.column_dimensions["A"].width = 20
                    worksheet.column_dimensions["B"].width = 58
                    worksheet.column_dimensions["C"].width = 15
                    for row in worksheet.iter_rows():
                        if all(cell.value == "" or cell.value is None for cell in row):  # Sprawdzanie, czy cały wiersz jest pusty
                            continue  # Pomijanie stylizacji dla pustych wierszy
                        for cell in row:
                            cell.font = czcionka
                            cell.border = thin_border
                            cell.alignment = alignment
                    max_col = worksheet.max_column
                    for cell in worksheet[1]:  # Pierwszy wiersz to nagłówki
                        cell.alignment = alignment_center
                        cell.font = czcioNagl
                    for row in worksheet.iter_rows(min_col=1, max_col=1, min_row=2):
                        for cell in row: # ustawienie na lewy srodek tekstu w kolumnie Identyfikator Kontroli
                            cell.alignment = Alignment(horizontal='left', vertical='center')
                    for row in worksheet.iter_rows(min_col=2, max_col=2, min_row=2):
                        for cell in row: # ustawienie na lewy srodek tekstu w kolumnie Nazwa kontroli
                            cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
                    for row in worksheet.iter_rows(min_col=max_col, max_col=max_col, min_row=2):
                        for cell in row: # ustawienie na prawy srodek tekstu w kolumnie Liczba Błędów
                            cell.alignment = Alignment(horizontal='right', vertical='center')
        
                    dataFrameWalidacja.to_excel(writer, sheet_name="Raport z walidacji", index=False)
                    worksheet = writer.sheets["Raport z walidacji"] # 3 arkusz
                    worksheet.column_dimensions["A"].width = 49
                    worksheet.column_dimensions["B"].width = 43
                    worksheet.column_dimensions["C"].width = 8
                    worksheet.column_dimensions["D"].width = 39
                    worksheet.column_dimensions["E"].width = 39
                    max_col = worksheet.max_column
                    max_row=worksheet.max_row
                    for row in worksheet.iter_rows():
                        for cell in row:
                            cell.font = czcionka
                            cell.border = thin_border
                            cell.alignment = alignment
                    for cell in worksheet[1]:  # Pierwszy wiersz to nagłówki
                        cell.alignment = alignment_center
                        cell.font = czcioNagl
                    for row in worksheet.iter_rows(min_col=1, max_col=max_col, min_row=2, max_row= max_row):
                        for cell in row:
                            cell.alignment = Alignment(
                                horizontal='left',
                                vertical='center',
                                wrap_text=True
                            )
        
                    dataFrameKontrola.to_excel(writer, sheet_name="Raport z kontroli dodatkowych", index=False)
                    worksheet = writer.sheets["Raport z kontroli dodatkowych"] # 4 arkusz
                    worksheet.column_dimensions["A"].width = 15.5
                    worksheet.column_dimensions["B"].width = 49
                    worksheet.column_dimensions["C"].width = 107
                    worksheet.column_dimensions["D"].width = 39
                    for row in worksheet.iter_rows():
                        for cell in row:
                            cell.font = czcionka
                            cell.border = thin_border
                            cell.alignment = alignment
                    for cell in worksheet[1]:  # Pierwszy wiersz to nagłówki
                        cell.alignment = alignment_center
                        cell.font = czcioNagl
                    for row in worksheet.iter_rows(min_col=1, max_col=1, min_row=2):
                        for cell in row: # ustawienie na lewy srodek tekstu w kolumnie Klasa
                            cell.alignment = Alignment(horizontal='left', vertical='center')
                    for row in worksheet.iter_rows(min_col=2, max_col=2, min_row=2):
                        for cell in row: # ustawienie na lewy srodek tekstu w kolumnie GMLID
                            cell.alignment = Alignment(horizontal='left', vertical='center')
                    for row in worksheet.iter_rows(min_col=3, max_col=3, min_row=2):
                        for cell in row: # ustawienie na lewy srodek tekstu w kolumnie Komunikat Błedu
                            cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
                    for row in worksheet.iter_rows(min_col=4, max_col=4, min_row=2):
                        for cell in row: # ustawienie na lewy srodek tekstu w kolumnie Grupa Kontroli
                            cell.alignment = Alignment(horizontal='left', vertical='center')
            except Exception as e:
                 print(f'Błąd w generacji pliku xlsx: {e}')
                 return
        
        if 'shp' in self.dlg.mComboBox.checkedItems():
            created_layers = {
                QgsWkbTypes.PointGeometry: False,
                QgsWkbTypes.LineGeometry: False,
                QgsWkbTypes.PolygonGeometry: False
                }
            for layer in QgsProject.instance().mapLayers().values():
                if layer.isValid() and isinstance(layer, QgsVectorLayer) and layer.featureCount() > 0:
                    if (
                        layer.name() in [
                            "błędy z kontroli atrybutów",
                            "błędne geometrie obiektów pokrycia terenu",
                            "lokalizacje błędów geometrii obiektów pokrycia terenu",
                            "nakładania w pokryciu terenu",
                            "dziury w pokryciu terenu",
                            "nakładania buforów poziomic",
                            "niespójność werteksów",
                            "lokalizacje błędów geometrii",
                            "lokalizacje kolizji OT_RTLW_L",
                            "lokalizacje błędów typu spike",
                            "przewerteksowanie"
                        ]
                        or "błędy z walidacji" in layer.name()
                    ):
                        if "błędy z kontroli atrybutów" in layer.name():
                            typ_geometrii = layer.geometryType()
                            # Jeśli warstwa dla tego typu geometrii została już stworzona, pomiń
                            if created_layers[typ_geometrii]:
                                continue
                            # Ustaw flagę na True, aby oznaczyć, że warstwa tego typu została już przetworzona
                            created_layers[typ_geometrii] = True
                            suffix = ''
                            if typ_geometrii == QgsWkbTypes.PointGeometry:
                                suffix = f"_Punkt"
                            elif typ_geometrii == QgsWkbTypes.LineGeometry:
                                suffix = f"_Linia"
                            elif typ_geometrii == QgsWkbTypes.PolygonGeometry:
                                suffix = f"_Poligon"
                            else:
                                suffix = f"_InnyTypGeometrii"
                            outSHP = (
                                f'{sciezkaGML}/RaportBledow_{nazwa_pliku}_'
                                f'{timestr}_błędy z kontroli atrybutów{suffix}.shp'
                            )
                        elif "błędy z walidacji" in layer.name():
                            outSHP = (
                                f'{sciezkaGML}/RaportBledow_{layer.name()}.shp'
                            ).replace('__', f'_{timestr}_')
        
                        else:
                            outSHP = (
                                f'{sciezkaGML}/RaportBledow_'
                                f'{nazwa_pliku}_{timestr}_{layer.name()}.shp'
                            )
                        options = QgsVectorFileWriter.SaveVectorOptions()
                        options.driverName = 'ESRI Shapefile'
                        options.layerName = layer.name()
                        options.fileEncoding = 'UTF-8'
                        options.destCRS = QgsCoordinateReferenceSystem(epsg)
                        options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile
                        error =  QgsVectorFileWriter.writeAsVectorFormatV3(layer, outSHP, QgsCoordinateTransformContext(), options)


    # walidacja pliku GML z conajmniej jednym obiektem


    def walidacja(self, task):
        """Wykonuje walidację pojedynczego pliku względem aktualnie wybranego schematu XSD."""
        global walidacjaZWynikiemPozytywnym, plikiZparsowane, plikGML, walidowanyPlik
        plikGML = pliki[int(task.description())]
        try:
            error_gmlid_dic = {}
            plikiZparsowane.append(plikGML)
            if self.dlg.comboBox.currentText() != 'EGIB':  # prostsza walidacja dla innych plików niż EGiB
                unique_gmlid = set()
                walidowanyPlik = lxml.etree.parse(plikGML)
                if walidowanyPlik.getroot() != None:
                    error_gmlid_dic = {}
                    for element in list(walidowanyPlik.iter()):
                        if str(element.tag).endswith('featureMember'):
                            wynikWalidacji = xmlschema.validate(element)
                            if not wynikWalidacji:
                                walidacjaZWynikiemPozytywnym = False
                                for subelement in element:
                                    error_gmlid_dic[xmlschema.error_log] = (
                                        subelement.attrib['{http://www.opengis.net/gml/3.2}id']
                                    )
        
                    return {'taskID':task.description(), 'plikGML':plikGML, 'error':error_gmlid_dic}
        
            else:
                 self.plikiZparsowane = plikiZparsowane # koniecznie na potrzeby pliku posredniego w EGIB
                 plikDoWalidacji = str(plikGML)
                 counter = 0
                 GML_IDS = ('{http://www.opengis.net/gml/3.2}id', '{http://www.opengis.net/gml}id')
        
        
                 def wyciagnijGmlId(node):
                     #Szuka GMLID w zależnosci od wersji standardu .gml
                     
                     """Wyciąga identyfikator GML z treści komunikatu błędu."""
                     for el in node.iter():
                         for key in GML_IDS:
                             if key in el.attrib:
                                 return el.attrib[key]
                             return 'BRAK_ID'
                 FakeXsdError = namedtuple('FakeXsdError', 'message line type_name')
        
        
                 def naBladWalidacji(msg, line=0, type_name='SCHEMAV_ELEMENT_CONTENT'):
                     # Tworzy „sztuczny” wpis błędu XSD, na przypadek gdy walidator nie zwrócił szczegółów.
                     
                     """Rejestruje pojedynczy błąd walidacji w strukturach raportowych."""
                     return FakeXsdError(msg, line, type_name)
        
        
                 def znajdzGmlId(el):
                     # Szuka GMLID w całym poddrzewie obiektu (featureMember), w wersjach GML 3.2 i 3.1.
                     # Zwraca pierwsze znalezione ID lub 'BRAK_ID'.
                     """Wyszukuje identyfikator GML w okolicy wskazanego wiersza pliku."""
                     ln = getattr(el, 'sourceline', None)
                     if ln:
                         return ln
                     for n in el.iter():
                         ln = getattr(n, 'sourceline', None)
                         if ln:
                             return ln
                         return 0
                 try:
                     with open(plikGML, 'rb', buffering=1024*1024) as f:
                         ctx = etree.iterparse(
                             f,
                             events=('end',),
                             tag='{*}featureMember',
                             huge_tree=True
                         )
                         for event, element in ctx:
                             # 1) zanim wyczyścimy element: pobieramy ID i ewentualny numer linii
                             gml_id = wyciagnijGmlId(element)
                             line_hint = znajdzGmlId(element)
                             try:
                                 first_child = next(iter(element), None)
                                 if first_child is not None:
                                     # najpierw próbujemy wersję 3.2, potem 3.1
                                     gml_id = (
                                         first_child.attrib.get(GML_IDS[0]) or
                                         first_child.attrib.get(GML_IDS[1]) or
                                         'BRAK_ID'
                                         )
                                     # jeśli nadal BRAK_ID, przeskanuj króciutko całe poddrzewo
                                 if gml_id == 'BRAK_ID':
                                     found = False
                                     for el in element.iter():
                                         val = el.attrib.get(GML_IDS[0]) or el.attrib.get(GML_IDS[1])
                                         if val:
                                             gml_id = val
                                             found = True
                                             break
                             except Exception as e:
                                 print(e)
                                 # nic – zachowujemy 'BRAK_ID' jak dotąd
                                 
                             # 2) Walidacja pojedynczego obiektu:
                                 #    - assertValid() waliduje i w razie błędów rzuca wyjątek z lokalnym logiem
                             try:
                                 xmlschema.assertValid(element)
                                 ok = True
                                 errs = [] # brak błędów dla tego obiektu
                             except Exception as e:
                                 ok = False
                                 errs = list(e.error_log)
        
                             if not ok:
                                 walidacjaZWynikiemPozytywnym = False
                                 if errs:
                                     error_gmlid_dic[tuple(errs)] = gml_id
        
                                 else:
                                     error_gmlid_dic[
                                         (
                                             naBladWalidacji(
                                                 "Walidacja nie powiodła się (szczegóły niedostępne)"
                                             ),
                                          line_hint
                                          )
                                     ] = gml_id
        
                             element.clear()
                             parent = element.getparent()
                             if parent is not None:
                                 while element.getprevious() is not None:
                                     del parent[0]
                             counter += 1
                             if counter % 50000 == 0:
                                 if hasattr(ctx, "root"):
                                     try:
                                         while ctx.root.getprevious() is not None:
                                             del ctx.root.getparent()[0]
                                     except Exception as e:
                                         print(e)
                 except Exception as e:
                     QMessageBox.critical(self.iface.mainWindow(), "Błąd walidacji", f"Walidacja przerwana:\n{e}")
                     return
                 return {'taskID':task.description(), 'plikGML':plikGML, 'error':error_gmlid_dic}
        
        except lxml.etree.XMLSyntaxError as error:
            walidacjaZWynikiemPozytywnym = False
            error_gmlid_dic = {}
            error_gmlid_dic[error] = ''
            return {'taskID':task.description(), 'plikGML':plikGML, 'error':error_gmlid_dic}
        except OSError as error:
            print(f"błąd OSError: {error}")
            walidacjaZWynikiemPozytywnym = False
            error_gmlid_dic = {}
            error_gmlid_dic[error] = ''
            return {'taskID':task.description(), 'plikGML':plikGML, 'error':error_gmlid_dic}
        except Exception as e:
            print(f"błąd w funkcji walidacji: {e}")


    def wynikiWalidacji(self, exception = None, value = None):
        """Przetwarza wynik walidacji i zapisuje informacje o błędach do struktur raportowych."""
        global walidowanePliki_df, wiersze_df, opisyBledow_df, komunikatyBledow_df, gmlid_df_w
        
        frazy = {"The value":"Wartość",
                 "failed to load external entity":"nie udało się załadować",
                 "Error reading file":"Błąd odczytu pliku",
                 "is not an element of the set":"nie znajduje się na liście",
                 "This element is not expected":"Ten element jest nieoczekiwany",
                 "Expected is one of":"Oczekiwana jest jedna wartość z ",
                 "Expected is":"Oczekiwany jest ",
                 "is not a valid value of the atomic type":"jest nieprawidłową wartością typu podstawowego",
                 "is not a valid value of the list type \'{http://www.opengis.net/gml/3.2}doubleList\'":" jest nieprawidłową wartością typu listowego \'{http://www.opengis.net/gml/3.2}doubleList\'",
                 "Opening and ending tag mismatch":"Błędny otwierający lub zamykający znacznik",
                 "line":"linia",
                 "and":"i",
                 "Start tag expected":"Oczekiwany tag początkowy",
                 "huge text node":"ogromny węzeł tekstowy",
                 "Point":"Punkt",
                 "column":"kolumna",
                 "is not a valid value of the union type":"jest nieprawdłową wartością typu unia",
                 "is less than the minimum value allowed":"jest mniejsza niż minimalna dozwolona wartość",
                 "Missing child element(s)":"Brakuje elementów dziecka/dzieci",
                 "EndTag":"Znacznik zamykający",
                 "must be greater than":"musi być większa niż",
                 "is not accepted by the pattern":"nie jest zgodna z paternem",
                 "is greater than the maximum value allowed":"jest większa niż maksymalna dozwolona liczba",
                 "not found": "nie został znaleziony",
                 "is not a valid value of the local atomic type":"nie jest poprawną wartością lokalnego typu podstawowego"
                 }
        if value != None:
            formats_to_check = ['xls', 'pdf', 'txt', 'shp', 'gpkg', 'xlsx']
            for errors in value['error']:
                if isinstance(errors, lxml.etree.XMLSyntaxError):
                    opisBledu = re.sub(r'{[^}]+}', '', str(errors))
                    for key in frazy:
                        opisBledu = opisBledu.replace(key,frazy[key])
                    msg =(
                        f"Błąd w składni pliku XML: {str(value['plikGML'])}\n"
                        f"- komunikat błędu: {opisBledu}\n\n"
                        )
                    if any(format in self.dlg.mComboBox.checkedItems() for format in formats_to_check):
                        walidowanePliki_df.append(str(value['plikGML']))
                        wiersze_df.append("-")
                        opisyBledow_df.append("Błąd w składni pliku XML")
                        komunikatyBledow_df.append(opisBledu)
                        gmlid_df_w.append('Nie pozyskano gmlid')
                elif isinstance(errors, OSError):
                    opisBledu = re.sub(r'{[^}]+}', '', str(errors))
                    for key in frazy:
                        opisBledu = opisBledu.replace(key,frazy[key])
                    if any(format in self.dlg.mComboBox.checkedItems() for format in formats_to_check):
                        walidowanePliki_df.append(str(value['plikGML']))
                        wiersze_df.append("-")
                        opisyBledow_df.append("-")
                        komunikatyBledow_df.append(opisBledu)
                        gmlid_df_w.append('Nie pozyskano gmlid')
                else:
                    for error in errors:
                        opisBledu = re.sub(r'{[^}]+}', '', error.message)
                        for key in frazy:
                            opisBledu = opisBledu.replace(key,frazy[key]) # tłumaczenie
                        msg = (
                            f"Walidacja pliku: {str(value['plikGML'])} z wynikiem negatywnym.\n"
                            f"- {config['KodyWalidacji'][error.type_name]}\n"
                            f"- wiersz: {str(error.line)}\n"
                            f"- komunikat błędu: {opisBledu}\n\n"
                        )
                        if any(format in self.dlg.mComboBox.checkedItems() for format in formats_to_check):
                            walidowanePliki_df.append(str(value['plikGML']))
                            wiersze_df.append(str(error.line))
                            opisyBledow_df.append(config['KodyWalidacji'][error.type_name])
                            komunikatyBledow_df.append(opisBledu)
                        gmlid_df_w.append(value['error'][errors])


    # otwarcie pliku raportu


    def otwarcieRaportuZWalidacji(self):
        """Otwiera raport walidacji zapisany w wybranym formacie."""
        for formatRaportu in self.dlg.mComboBox.checkedItems():
            path = (
                f'{sciezkaGML}/RaportBledow_'
                f'{str(nazwa_pliku)}_{timestr}'
                f'.{formatRaportu}'
            )
            if os.path.isfile(path):
                QDesktopServices.openUrl(QUrl.fromLocalFile(path))


    # import plików GML do QGIS


    def importGML(self, zparsowanyPlik):
        """Importuje wskazany plik GML do projektu QGIS jako warstwy robocze."""
        global epsg
        gdal.SetConfigOption('GML_ATTRIBUTES_TO_OGR_FIELDS', 'YES')
        nazwaPliku = str(pathlib.Path(zparsowanyPlik).name)[:-4]
        root = QgsProject.instance().layerTreeRoot()
        typyGeometrii = {
            0: 'Geometry',
            1: 'Point',
            2: 'LineString',
            3: 'Polygon',
            4: 'MultiPoint',
            5: 'MultiLineString',
            6: 'MultiPolygon',
            8: 'CircularString',
            9: 'CompoundCurve',
            10: 'CurvePolygon',
            11: 'MultiCurve',
            12: 'MultiSurface',
            13: 'Curve',
            100: 'No Geometry',
            1001: 'PointZ',
            -2147483647: 'Point',
            -2147483648: 'Point'
            }
        groupaGlowna = getattr(self, "groupaGlowna", None)
        if groupaGlowna is None:
            QgsMessageLog.logMessage(
                "Brak self.groupaGlowna – przerwam importGML",
                "Walidator plików GML",
                Qgis.Warning
            )
            return
        warstwy = [x.GetName() for x in ogr.Open(zparsowanyPlik)]
        data_source = ogr.Open(zparsowanyPlik)
        typGeometriiWarstw = [x.GetGeomType() for x in ogr.Open(zparsowanyPlik)]
        for warstwa, feature_geom in zip(warstwy, typGeometriiWarstw):
            if warstwa in nazwaPliku:
                nazwaWarstwy = nazwaPliku
            else:
                nazwaWarstwy = nazwaPliku + '_' + warstwa
            if feature_geom == 100: # brak geometrii
                qgis_layer = QgsVectorLayer(
                    f'{zparsowanyPlik}|layername={warstwa}',
                    nazwaWarstwy,
                    'ogr'
                )
                QgsProject.instance().addMapLayer(qgis_layer, False)
                groupaGlowna.addLayer(qgis_layer)
                qgis_layer = None
            elif feature_geom == 0: # GeometryPropertyType
                tablica_typow_geometrii = [1,2,3]
                if warstwa in [
                        'EGB_ObrebEwidencyjny',
                        'EGB_Budynek',
                        'EGB_JednostkaEwidencyjna', # EGiB
                        'OT_Ogrodzenia',
                        'OT_BudynekNiewykazanyWEGIB', # BDOT500
                        'NG_NazwaGeograficznaSwiata',
                        'NG_NazwaGeograficznaRP' # PRNG
                    ]:
                    tablica_typow_geometrii = [4,5,6]
        
                for typGeometryPropertyType in tablica_typow_geometrii:
                    qgis_layer = None
                    qgis_layer = QgsVectorLayer(
                        (
                            f'{zparsowanyPlik}|layername={warstwa}'
                            f'|geometrytype={typyGeometrii[typGeometryPropertyType]}'
                        ),
                        f'{nazwaWarstwy}_{str(typGeometryPropertyType)}',
                        'ogr'
                    )
                    epsg = qgis_layer.crs().authid()
                    if not self.dlg.checkBox.isChecked():
                        attributes = qgis_layer.fields().names()
                        if 'koniecWersjiObiektu' in attributes:
                            exp = 'koniecWersjiObiektu is NULL'
                            qgis_layer = QgsVectorLayer(
                                (
                                    f'{zparsowanyPlik}|layername={warstwa}|geometrytype='
                                    f'{typyGeometrii[typGeometryPropertyType]}|subset={exp}'
                                ),
                                f'{nazwaWarstwy}_{str(typGeometryPropertyType)}',
                                'ogr'
                            )
                        elif 'koniecWersjaObiekt' in attributes:
                            exp = 'koniecWersjaObiekt is NULL'
                            qgis_layer = QgsVectorLayer(
                                (
                                    f'{zparsowanyPlik}|layername={warstwa}'
                                    f'|geometrytype={typyGeometrii[typGeometryPropertyType]}|subset={exp}'
                                ),
                                f'{nazwaWarstwy}_{str(typGeometryPropertyType)}',
                                'ogr'
                                )
                    if qgis_layer.featureCount() > 0:
                        QgsProject.instance().addMapLayer(qgis_layer, False)
                        groupaGlowna.addLayer(qgis_layer)
            else:
                qgis_layer = QgsVectorLayer(
                    (
                        f'{zparsowanyPlik}|layername={warstwa}'
                        f'|geometrytype={typyGeometrii[feature_geom]}'
                    ),
                    nazwaWarstwy,
                    'ogr'
                )
                epsg = qgis_layer.crs().authid()
                if not self.dlg.checkBox.isChecked():
                    attributes = qgis_layer.fields().names()
                    if 'koniecWersjiObiektu' in attributes:
                        exp = 'koniecWersjiObiektu is NULL'
                        qgis_layer = QgsVectorLayer(
                            (
                                f'{zparsowanyPlik}|layername={warstwa}'
                                f'|geometrytype={typyGeometrii[feature_geom]}|subset={exp}'
                             ),
                            nazwaWarstwy,
                            'ogr',
                    )
                    elif 'koniecWersjaObiekt' in attributes:
                        exp = 'koniecWersjaObiekt is NULL'
                        qgis_layer = QgsVectorLayer(
                            (
                             f'{zparsowanyPlik}|layername={warstwa}'
                             f'|geometrytype={typyGeometrii[feature_geom]}|subset={exp}'
                          ),
                         nazwaWarstwy,
                         'ogr',
                 )
                if qgis_layer.featureCount() > 0:
                    QgsProject.instance().addMapLayer(qgis_layer, False)
                    groupaGlowna.addLayer(qgis_layer)
                    qgis_layer = None


    # wczytanie szablonu kontroli


    def wczytanieSzablonuKontroli(self):
        """Wczytuje aktualny szablon kontroli i buduje model drzewa kontroli."""
        global modelKontroli, wersjaSzablonuKontroli, sumaKontrolaSchemat, szablonKontroliPath
        global sumaKontrolaSzablonKontroli, sumaKontrolnaWskazanyPlik, SumaKontrolnaDaneZrodlowe
        
        szablonKontroliPath = self.sciezkaDoWskazanegoSzablonuKontroli(
            self.dlg.comboBox_2.currentText(),
            self.dlg.comboBox.currentText()
        )
        buf_size = 5000000  # Czytaj plik w blokach o rozmiarze 64KB
        crc = 0
        with open(xsdPath, 'rb') as f:
            while True:
                data = f.read(buf_size)
                if not data:
                    break
                crc = binascii.crc32(data, crc)
        
        sumaKontrolaSchemat = "%08X" % (crc & 0xFFFFFFFF) # tworzenie sumu kontrolnej dla schematu
        tree = et.parse(szablonKontroliPath)
        root = tree.getroot()
        tree1 = et.parse(xsdPath)
        root1 = tree1.getroot()
        wersjaSzablonuKontroli = root.get('version')
        wersjaSchematu = root1.get('version')
        filename = os.path.basename(xsdPath) # pobiera sama nazwę pliku
        metaSchemat = os.path.splitext(filename)[0] + " " +  wersjaSchematu
        crc = 0
        with open(szablonKontroliPath, 'rb') as f:
            while True:
                data = f.read(buf_size)
                if not data:
                    break
                crc = binascii.crc32(data, crc)
        sumaKontrolaSzablonKontroli = "%08X" % (crc & 0xFFFFFFFF)
        # tworzenie sumu kontrolnej dla szablonu kontroli
        modelKontroli = QStandardItemModel()
        modelKontroli.setHorizontalHeaderLabels(['Lista kontroli'])
        self.modelKontroli = modelKontroli
        self.dlg.treeView.setModel(modelKontroli)
        self.dlg.treeView.setUniformRowHeights(True)
        self.dlg.treeView.setFocusPolicy(Qt.StrongFocus)
        self.zastosujRozmiarCzcionkiKontroli()
        
        i = 0
        for grupaKontroli in root:
            parent = QStandardItem(grupaKontroli.get('name'))
            modelKontroli.appendRow(parent)
            parent.setCheckable(True)
            parent.setCheckState(0)
            for kontrola in grupaKontroli:
                child = QStandardItem(kontrola.get('name'))
                child.setData(kontrola.get('id'), 1)
                child.setData(kontrola.get('name'), 2)
                child.setData(kontrola.get('class'), 3)
                child.setData(kontrola.get('errorPhrase'), 4)
                for k in kontrola:
                    child.setData(k.get('typ'), 5)
                    child.setData(k.get('sql'), 6)
                child.setData(kontrola.get('class2'), 7)
                child.setData(kontrola.get('joinFieldName'), 8)
                child.setData(kontrola.get('targetFieldName'), 9)
                child.setData(kontrola.get('joinFieldNamesSubset'), 11)
                child.setCheckable(True)
                child.setCheckState(0)
                parent.appendRow([child])
            self.dlg.treeView.setFirstColumnSpanned(i, self.dlg.treeView.rootIndex(), True)
            i += 1
        modelKontroli.itemChanged.connect(self.zmianaStatusuKontroli)
        self.wersjaSzablonuKontroli = wersjaSzablonuKontroli
        self.podlaczGrupoweZaznaczanieKontroli()
        self.odtworzWybraneKontrole()


    def zaznaczKontroleNaPodstawieDanych(self):
        """Zaznacza kontrole możliwe do wykonania na podstawie rozpoznanych danych."""
        try:
            self.odznaczWszystkieKontrole()
            files_str = ", ".join(files)
            for i in range(modelKontroli.rowCount()):
                item = modelKontroli.item(i)
                if item.checkState() in (0,1):
                    for child in range(item.rowCount()):
                        if files_str.__contains__(item.child(child).data(3)):
                            item.child(child).setCheckState(2)
        except Exception as e:
            print('błąd w zaznaczKontroleNaPodstawieDanych:',e)


    def zaznaczWszystkieKontrole(self):
        """Zaznacza wszystkie kontrole widoczne w drzewie kontroli."""
        try:
            for i in range(modelKontroli.rowCount()):
                item = modelKontroli.item(i)
                if item.checkState() in (0,1):
                    item.setCheckState(2)
        except Exception as e:
             print('błąd w zaznaczWszystkieKontrole:',e)


    def odznaczWszystkieKontrole(self):
        """Odznacza wszystkie kontrole widoczne w drzewie kontroli."""
        try:
            for i in range(modelKontroli.rowCount()):
                item = modelKontroli.item(i)
                if item.checkState() in (1,2):
                    item.setCheckState(0)
        except Exception as e:
            print('błąd w odznaczWszystkieKontrole:',e)


    def zmianaStatusuKontroli(self, item):
        """Aktualizuje stan rodzica i elementów potomnych po zmianie zaznaczenia kontroli."""
        global blokady
        statusParent = item.checkState()
        if (
            not item.hasChildren()
            and self.dlg.comboBox.currentText() == 'BDOT10k'
            and (
                item.data(6).__contains__("Granice_powiatow")
                or item.data(6).__contains__("czyObiektyWewnatrzPowiatu")
                or item.data(6).__contains__("minimalna")
                or item.data(6).__contains__("fullCoverage")
                or item.data(6).__contains__("minDlugosc")
                or item.data(6).__contains__("kontrolaZgodnosciZDanymiGDOS")
            )
        ):
            blokady[2] = 0
            for i in range(self.dlg.treeView.model().rowCount()):
                parent_item = self.dlg.treeView.model().item(i)
                for j in range(parent_item.rowCount()):
                    child_item = parent_item.child(j)
                    if (
                            len(self.dlg.mQgsFileWidget_gp.filePath()) == 0
                            and child_item.checkState() == 2
                            and any(
                                keyword in child_item.data(6)
                                for keyword in [
                                    "Granice_powiatow",
                                    "czyObiektyWewnatrzPowiatu",
                                    "fullCoverage",
                                    "minimalna",
                                    "minDlugosc",
                                    "kontrolaZgodnosciZDanymiGDOS"
                                    ]
                            )
                        ):
                        blokady[2] = 1
                        self.dlg.button_box.buttons()[0].setEnabled(False)
        
        if (
                not item.hasChildren()
                and self.dlg.comboBox.currentText() == 'BDOT10k'
                and item.data(6).__contains__("Granice_gmin")
            ):
            blokady[3] = 0
            if len(self.dlg.mQgsFileWidget_gg.filePath()) == 0 and item.checkState() == 2:
               blokady[3] = 1
               self.dlg.button_box.buttons()[0].setEnabled(False)
        
        if (
                not item.hasChildren()
                and self.dlg.comboBox.currentText() == 'BDOT10k'
                and item.data(6).__contains__("Granice_jednostek_ewidencyjnych")
            ):
            blokady[4] = 0
            if len(self.dlg.mQgsFileWidget_gje.filePath()) == 0 and item.checkState() == 2:
                blokady[4] = 1
                self.dlg.button_box.buttons()[0].setEnabled(False)
        
        if (
                not item.hasChildren()
                and self.dlg.comboBox.currentText() == 'BDOT10k'
                and item.data(6).__contains__("(gml,gml)")
            ):
            blokady[5] = 0
            for j in range(item.parent().rowCount()):
                if (
                        len(self.dlg.mQgsFileWidget_dz.filePath()) == 0
                        and item.parent().child(j).checkState() == 2
                        and item.parent().child(j).data(6).__contains__("(gml,gml)")
                    ):
                    blokady[5] = 1
                    self.dlg.button_box.buttons()[0].setEnabled(False)
        
        if (
                not item.hasChildren()
                and self.dlg.comboBox.currentText() == 'BDOT10k'
                and item.data(6).__contains__("prng_miejscowosci")
            ):
            blokady[6] = 0
            for j in range(item.parent().rowCount()):
                if (
                        len(self.dlg.mQgsFileWidget_prng_m.filePath()) == 0
                        and item.parent().child(j).checkState() == 2
                        and item.parent().child(j).data(6).__contains__("prng_miejscowosci")
                    ):
                    blokady[6] = 1
                    self.dlg.button_box.buttons()[0].setEnabled(False)
        
        if (
                not item.hasChildren()
                and self.dlg.comboBox.currentText() == 'BDOT10k'
                and item.data(6).__contains__("prng_obiektyfizjograficzne")
            ):
            blokady[7] = 0
            for j in range(item.parent().rowCount()):
                if (
                        len(self.dlg.mQgsFileWidget_prng_o.filePath()) == 0
                        and item.parent().child(j).checkState() == 2
                        and item.parent().child(j).data(6).__contains__("prng_obiektyfizjograficzne")
                    ):
                    blokady[7] = 1
                    self.dlg.button_box.buttons()[0].setEnabled(False)
        
        if (
                not item.hasChildren()
                and self.dlg.comboBox.currentText() == 'BDOT10k'
                and item.data(6).__contains__("tereny_chronione")
            ):
            blokady[9] = 0
            for j in range(item.parent().rowCount()):
                if (
                        len(self.dlg.mQgsFileWidget_tc.filePath()) == 0
                        and item.parent().child(j).checkState() == 2
                        and item.parent().child(j).data(6).__contains__("tereny_chronione")
                   ):
                   blokady[9] = 1
                   self.dlg.button_box.buttons()[0].setEnabled(False)
        
        if not item.hasChildren() and self.dlg.comboBox.currentText() == 'BDOT10k' and item.data(6).__contains__("ulic_gus"):
            blokady[8] = 0
            for j in range(item.parent().rowCount()):
                 if (
                         len(self.dlg.mQgsFileWidget_ulic.filePath()) == 0
                         and item.parent().child(j).checkState() == 2
                         and item.parent().child(j).data(6).__contains__("ulic_gus")
                     ):
                     blokady[8] = 1
                     self.dlg.button_box.buttons()[0].setEnabled(False)
        if (
                not item.hasChildren()
                and self.dlg.comboBox.currentText() == 'BDOT10k'
                and item.data(6).__contains__("simc_gus")
            ):
            blokady[10] = 0
            for j in range(item.parent().rowCount()):
                 if (
                         len(self.dlg.mQgsFileWidget_simc.filePath()) == 0
                         and item.parent().child(j).checkState() == 2
                         and item.parent().child(j).data(6).__contains__("simc_gus")
                    ):
                     blokady[10] = 1
                     self.dlg.button_box.buttons()[0].setEnabled(False)
        
        if sum(blokady) == 0:
            self.dlg.button_box.buttons()[0].setEnabled(True)
        
        if item.rowCount() > 0:
            for j in range(item.rowCount()):
                if statusParent == 2:
                    item.child(j).setCheckState(2)
                if statusParent == 0:
                    item.child(j).setCheckState(0)
        else:
            suma = 0
            for j in range(item.parent().rowCount()):
                suma += item.parent().child(j).checkState()
            if suma == 0:
                item.parent().setCheckState(0)
            elif suma == item.parent().rowCount() * 2:
                item.parent().setCheckState(2)
            else:
                item.parent().setCheckState(1)
        
        self.zapiszWybraneKontrole()


   # kontrola atrybutów


    def kontrolaAtrybutow(self):
        """Uruchamia kontrole atrybutowe dla aktualnie zaimportowanych danych."""
        if self.dlg.comboBox.currentText() != 'EGIB':
            self.kontrolaAtrybutow_inne()
        else:
            slownikBledow.clear()
            komunikatyBledowKontroli_df.clear()
            kontrolowanePliki_df.clear()
            gmlid_df_k.clear()
            klasy_df.clear()
            grupyKontroli_df.clear()
            self.idkontroli_LiczbaBledow.clear()
            self.kontrolaAtrybutow_EGIB()


    def kontrolaAtrybutow_inne(self):
        """Wykonuje kontrole atrybutowe dla baz innych niż EGIB."""
        global liczbaKontroliWykonanych, progress, slownikBledow, warstwyBledowKontroliAtrybutow, kontroleWykonaniePojedyncze
        global idkontroli_LiczbaBledow, warstwa, kontrolePojedynczyKomunikat, idKontroli
        geometryType = {0:'Point',1:'Line',2:'Polygon'}
        kontrolePojedynczyKomunikat = {"topo_e3_k6":False,"topo_e4_k27":False,"topo_e4_k25":False}
        QgsProjectInstance = QgsProject().instance()
        QApplication.processEvents()
        
        for i in range(modelKontroli.rowCount()):
            parent = modelKontroli.item(i)
            if parent.checkState() in (1,2):
                for j in range(parent.rowCount()):
                    if (
                            parent.child(j).checkState() == 2
                            and parent.child(j).data(1) != ''
                            and parent.child(j).data(2) != ''
                            and parent.child(j).data(3) != ''
                            and parent.child(j).data(4) != ''
                            and parent.child(j).data(5) in (
                                'QgsExpression',
                                'QgsExpressionWithJoin',
                                'pythonFunction',
                                'PyExpression',
                                'MgrExpression',
                                )
                            and parent.child(j).data(6) != ''
                        ):
                        nazwaKontroli = parent.data(2)
                        errorPhrase = parent.child(j).data(4)
                        idKontroli = parent.child(j).data(1)
                        sqltxt = parent.child(j).data(6)
                        sqltxt = sqltxt.replace("&gt;",">")
                        sqltxt = sqltxt.replace("&lt;","<")
                        klasa = parent.child(j).data(3)
                        klasaDoRaportowania = klasa
        
                        if len(plikiZparsowane) == 1:
                            parsedFileName = str(pathlib.Path(plikiZparsowane[0]).name)
                            parsedFileBaseName = parsedFileName[:-4]
        
                        layers_in_group = [node.layer() for node in self.groupaGlowna.children() if isinstance(node, QgsLayerTreeLayer)]
                        for w in layers_in_group:
                            if klasa in w.name() and not kontrolePojedynczyKomunikat.get(idKontroli, False):
                                nazwaWarstwy = w.name()
                                zparsowanyPlik = None
                                if len(plikiZparsowane) > 0:
                                    for plikZparsowany in plikiZparsowane:
                                        parsedFileName_TMP = str(pathlib.Path(plikZparsowany).name)
                                        if klasa in parsedFileName_TMP and nazwaWarstwy in parsedFileName_TMP:
                                            zparsowanyPlik = plikZparsowany
                                            parsedFileName = parsedFileName_TMP
                                            parsedFileBaseName = parsedFileName[:-4]
                                            break
                                    if zparsowanyPlik is None:
                                        zparsowanyPlik = plikZparsowany
                                if self.dlg.comboBox.currentText() == 'BDOT10k':
                                    teryt = parsedFileName[:-15][-4:]
                                else:
                                    teryt = None
                                try:
                                    for warstwaWsqltext in re.findall(r"layer:='(.*?)'", sqltxt):
                                        sqltxt = sqltxt.replace(
                                            f"layer:='{warstwaWsqltext}'",
                                            f"layer:='{parsedFileName[:-13]}{warstwaWsqltext}'_'{warstwaWsqltext}"
                                        )
                                except Exception as e:
                                    print(f"błąd przy tworzeniu warstwy: {e}")
        
                                try:
                                    layerL1 = QgsProjectInstance.mapLayersByName(nazwaWarstwy)[0]
                                except:
                                    layerL1 = None
        
                                if parent.child(j).data(5) in (
                                        'QgsExpression',
                                        'QgsExpressionWithJoin',
                                        'pythonFunction',
                                        'PyExpression',
                                        'MgrExpression'
                                        ) and layerL1 is not None:
                                    try:
        
                                        czyWarstwaIstnieje = False
                                        for lyr in QgsProjectInstance.mapLayers().values():
                                            if lyr.name() == nazwaWarstwy:
                                                if czyWarstwaIstnieje:
                                                    QgsProject.instance().removeMapLayer(lyr)
                                                else:
                                                    czyWarstwaIstnieje = True
                                        if not czyWarstwaIstnieje:
                                            liczbaKontroliWykonanych += 1
                                            progress.setValue(liczbaKontroliWykonanych)
                                            continue
                                        layer = QgsProjectInstance.mapLayersByName(nazwaWarstwy)[0]
                                        requestFeatures = []
                                        if parent.child(j).data(5) in ('QgsExpression','QgsExpressionWithJoin'):
                                            start_time = datetime.now()
                                            request = QgsFeatureRequest(QgsExpression(sqltxt))
                                            requestFeatures = layer.getFeatures(request)
                                            end_time = datetime.now()
                                            czas_przetwarzania = end_time - start_time
                                            if klasa == 'OT':
                                                QgsMessageLog.logMessage(
                                                    f'{parent.child(j).data(1)} - {klasa + parsedFileName[-11:-4]} - {czas_przetwarzania}',
                                                    tag="Walidator plików GML",
                                                    level=Qgis.Info
                                                    )
                                            else:
                                                QgsMessageLog.logMessage(
                                                    f'{parent.child(j).data(1)} - {klasa} - {czas_przetwarzania}',
                                                    tag="Walidator plików GML",
                                                    level=Qgis.Info
                                                    )
                                        else:
                                            start_time = datetime.now()
                                            layerTMP = QgsProjectInstance.mapLayersByName(nazwaWarstwy)
                                            if sqltxt.count('(teryt)') > 0:
                                                requestFeatures = globals().get(sqltxt.replace('(teryt)', ''))(layerTMP[0], teryt)
                                            elif sqltxt.count('(gml)') > 0:
                                                requestFeatures = globals().get(sqltxt.replace('(gml)', ''))(layerTMP[0], lxml.etree.parse(zparsowanyPlik))
                                            elif sqltxt.count('(gml,klasa)') > 0:
                                                requestFeatures = globals().get(sqltxt.replace('(gml,klasa)', ''))(layerTMP[0], lxml.etree.parse(zparsowanyPlik), klasa)
                                            elif sqltxt.count('(gml,gml)') > 0:
                                                plikGMLzrodlowy = self.zwrocPlikZrodlowy(parsedFileName[-11:-4])
                                                if plikGMLzrodlowy is not None:
                                                    requestFeatures = globals().get(sqltxt.replace('(gml,gml)', ''))(layerTMP[0], plikGMLzrodlowy, zparsowanyPlik)
                                                else:
                                                    requestFeatures = []
                                            elif '(ulic_gus)' in sqltxt:
                                                requestFeatures = globals().get(sqltxt.replace('(ulic_gus)', ''))(layerTMP[0], ulic_gus)
                                            elif '(ulic_gus,simc_gus)' in sqltxt:
                                                requestFeatures = globals().get(sqltxt.replace('(ulic_gus,simc_gus)', ''))(layerTMP[0], ulic_gus, simc_gus)
                                            elif '(prng_obiektyfizjograficzne)' in sqltxt:
                                                requestFeatures = globals().get(sqltxt.replace('(prng_obiektyfizjograficzne)', ''))(layerTMP[0], prng_obiektyfizjograficzne, klasa)
                                            elif '(prng_miejscowosci)' in sqltxt:
                                                requestFeatures = globals().get(sqltxt.replace('(prng_miejscowosci)', ''))(layerTMP[0], prng_miejscowosci, klasa)
                                            elif '(tereny_chronione)' in sqltxt:
                                                requestFeatures = globals().get(sqltxt.replace('(tereny_chronione)', ''))(layerTMP[0], tereny_chronione, klasa)
                                            elif parent.child(j).data(5) == 'PyExpression':
                                                requestFeatures = pyExpression(layerTMP[0], sqltxt)
                                            elif parent.child(j).data(5) == 'MgrExpression':
                                                requestFeatures = mgrExpression(layerTMP[0], lxml.etree.parse(zparsowanyPlik), sqltxt)
                                            elif 'kontrolaFormatuAtrybutuWysokosc' in sqltxt:
                                                requestFeatures = kontrolaFormatuAtrybutuWysokosc(
                                                    layerTMP[0],
                                                    sqltxt.replace('kontrolaFormatuAtrybutuWysokosc',''),
                                                    lxml.etree.parse(zparsowanyPlik)
                                                    )
                                            elif idKontroli in kontroleWykonaniePojedyncze:
                                                if not kontroleWykonaniePojedyncze[idKontroli]:
                                                    if klasa == 'OT_PT':
                                                        if not kontroleWykonaniePojedyncze[idKontroli]:
                                                            requestFeatures = fullCoverage(layerTMP[0])
                                                            kontroleWykonaniePojedyncze[idKontroli] = True
                                                        else:
                                                            continue
                                                    else:
                                                        requestFeatures = kompletnoscPlikowBDOT10k(files)
                                                        kontroleWykonaniePojedyncze[idKontroli] = True
                                            else:
                                                requestFeatures = globals().get(sqltxt)(layerTMP[0])
        
                                            end_time = datetime.now()
                                            czas_przetwarzania = end_time - start_time
                                            if klasa == 'OT':
                                                QgsMessageLog.logMessage(
                                                    f'{parent.child(j).data(1)} - {klasa + parsedFileName[-11:-4]} - {czas_przetwarzania}',
                                                    tag="Walidator plików GML",
                                                    level=Qgis.Info
                                                    )
                                            else:
                                                QgsMessageLog.logMessage(
                                                    f'{parent.child(j).data(1)} - {klasa} - {czas_przetwarzania}',
                                                    tag="Walidator plików GML",
                                                    level=Qgis.Info
                                                    )
        
                                        selected_features = []  # lista z wybranymi obiektami
        
                                        liczbaBledow = 0
                                        for feature in requestFeatures:
                                            if idKontroli not in 'topo_e3_k6' or not kontrolePojedynczyKomunikat[idKontroli]:
                                                liczbaBledow += 1
                                                gmlid = feature['gml_id'] if 'gml_id' in feature.fields().names() else 'nie dotyczy'
                                                # Sprawdzenie czy klucz errorPhrase już istnieje w słowniku
                                                if errorPhrase not in slownikBledow:
                                                    slownikBledow[errorPhrase] = []
                                                else:
                                                    slownikBledow[errorPhrase].append(feature)
                                                typ_geometrii = QgsWkbTypes.displayString(feature.geometry().wkbType())
        
                                                if typ_geometrii == 'Point':
                                                    warstwa = warstwyBledowKontroliAtrybutow['Point']
                                                elif typ_geometrii in (
                                                        'LineString',
                                                        'CompoundCurve',
                                                        'CircularString',
                                                        'MultiLineString'
                                                    ):
                                                    warstwa = warstwyBledowKontroliAtrybutow['LineString']
                                                elif typ_geometrii in (
                                                        'Polygon',
                                                        'CurvePolygon',
                                                        'MultiPolygon'
                                                    ):
                                                    warstwa = warstwyBledowKontroliAtrybutow['Polygon']
                                                if idKontroli in ['topo_e1_k23_4'] and klasa == 'OT_SKJZ_L':
                                                    klasaDoRaportowania = 'OT_SK/OT_PTPL'
                                                    gmlid = feature['gml_id'] if 'gml_id' in feature.fields().names() else 'nie dotyczy'
        
                                                if idKontroli in ['topo_e5_k1']:
                                                    klasaDoRaportowania = gmlid
                                                    gmlid = 'nie dotyczy'
                                                if klasa == 'OT':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_TC':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_SW':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_SK':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_BU':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_SU':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_OI':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_BUHD':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_BUWT':
                                                    klasaDoRaportowania = 'OT' + parsedFileName[-11:-4]
                                                if klasa == 'OT_PT':
                                                    klasaDoRaportowania = 'nie dotyczy'
                                                    gmlid = 'nie dotyczy'
                                                if feature['gml_id'].__contains__("|"):
                                                    gmlid = feature['gml_id'].split("|")[0]
                                                    errorPhrase_fin = errorPhrase + feature['gml_id'].split("|")[1]
                                                else:
                                                    errorPhrase_fin = errorPhrase
        
                                                feature.setAttributes([gmlid, klasaDoRaportowania, errorPhrase_fin])
                                                if typ_geometrii != 'Unknown':
                                                    warstwa.dataProvider().addFeatures([feature])
                                                    QgsProject.instance().addMapLayer(warstwa)
        
                                            if idKontroli not in kontrolePojedynczyKomunikat or not kontrolePojedynczyKomunikat[idKontroli]:
                                                komunikatyBledowKontroli_df.append(errorPhrase_fin)
                                                kontrolowanePliki_df.append(parsedFileName)
                                                gmlid_df_k.append(gmlid)
                                                klasy_df.append(klasaDoRaportowania)
                                                grupyKontroli_df.append(nazwaKontroli)
                                                if idKontroli in kontrolePojedynczyKomunikat and not kontrolePojedynczyKomunikat[idKontroli]:
                                                    kontrolePojedynczyKomunikat[idKontroli] = True
        
                                        if liczbaBledow > 0:
                                            if parent.child(j).data(1) not in idkontroli_LiczbaBledow:
                                                idkontroli_LiczbaBledow[parent.child(j).data(1)] = liczbaBledow
                                            else:
                                                idkontroli_LiczbaBledow[parent.child(j).data(1)] += liczbaBledow
        
                                    except Exception as e:
                                        print(f'Błąd przy wykonaniu {parent.child(j).data(1)}: {e}')
                                    liczbaKontroliWykonanych += 1
                                    progress.setValue(liczbaKontroliWykonanych)


    def kontrolaWybranychFormatowRaportow(self, txt):
        """Sprawdza poprawność wyboru formatów raportów i blokuje niedozwolone kombinacje."""
        global blokady
        if txt == []:
            QMessageBox.critical(
                QMessageBox(),
                'Formaty raportów',
                'Musi być wybrany co najmniej jeden format raportu.',
                QMessageBox.Ok
            )
            blokady[1] = 1
            self.dlg.button_box.buttons()[0].setEnabled(False)
        else:
            blokady[1] = 0
            if sum(blokady) == 0:
                self.dlg.button_box.buttons()[0].setEnabled(True)


    def zwrocPlikZrodlowy(self, klasa):
        """Zwraca ścieżkę pliku źródłowego powiązanego z daną kontrolą."""
        plikGMLzrodlowy = None
        if len(sciezkiPlikowZrodlowych) > 0:
            for sciezkaPlikuZrodlowego in sciezkiPlikowZrodlowych:
                    if klasa in sciezkaPlikuZrodlowego:
                        if 'plikZIPzrodlowy' in globals():
                            plikGMLzrodlowy = plikZIPzrodlowy.extract(
                                sciezkaPlikuZrodlowego,
                                os.path.join(
                                    sciezkaGML,
                                    "daneZrodlowe"
                                    )
                                )
                        else:
                             # gdy użytkownik podał pojedynczy .gml/.xml
                             plikGMLzrodlowy = sciezkaPlikuZrodlowego
                        break
        return plikGMLzrodlowy


    def zapisDoGPKG(self):
        """Zapisuje warstwy błędów do pliku GPKG, gdy wybrano taki format raportu."""
        if 'gpkg' in self.dlg.mComboBox.checkedItems():
            created_layers = {
                QgsWkbTypes.PointGeometry: False,
                QgsWkbTypes.LineGeometry: False,
                QgsWkbTypes.PolygonGeometry: False
                }
            for layer in QgsProject.instance().mapLayers().values():
                if layer.isValid() and isinstance(layer, QgsVectorLayer) and layer.featureCount() > 0:
                    if (
                        layer.name() in [
                            "błędy z kontroli atrybutów",
                            "błędne geometrie obiektów pokrycia terenu",
                            "lokalizacje błędów geometrii obiektów pokrycia terenu",
                            "nakładania w pokryciu terenu",
                            "dziury w pokryciu terenu",
                            "nakładania buforów poziomic",
                            "niespójność werteksów",
                            "lokalizacje błędów geometrii",
                            "lokalizacje kolizji OT_RTLW_L",
                            "lokalizacje błędów typu spike",
                            "przewerteksowanie"
                        ]
                        or "błędy z walidacji" in layer.name()
                    ):
                        if "błędy z kontroli atrybutów" in layer.name():
                            typ_geometrii = layer.geometryType()
                            # Jeśli warstwa dla tego typu geometrii została już stworzona, pomiń
                            if created_layers[typ_geometrii]:
                                continue
                            # Ustaw flagę na True, aby oznaczyć, że warstwa tego typu została już przetworzona
                            created_layers[typ_geometrii] = True
                            suffix = ''
                            if typ_geometrii == QgsWkbTypes.PointGeometry:
                                suffix = f"_Punkt"
                            elif typ_geometrii == QgsWkbTypes.LineGeometry:
                                suffix = f"_Linia"
                            elif typ_geometrii == QgsWkbTypes.PolygonGeometry:
                                suffix = f"_Poligon"
                            else:
                                suffix = f"_InnyTypGeometrii"
                            outGPKG = (
                                f'{sciezkaGML}/RaportBledow_{nazwa_pliku}_'
                                f'{timestr}_błędy z kontroli atrybutów{suffix}.gpkg'
                            )
                        elif "błędy z walidacji" in layer.name():
                            outGPKG = (
                                f'{sciezkaGML}/RaportBledow_{layer.name()}.gpkg'
                            ).replace('__', f'_{timestr}_')
        
                        else:
                            outGPKG = (
                                f'{sciezkaGML}/RaportBledow_'
                                f'{nazwa_pliku}_{timestr}_{layer.name()}.gpkg'
                            )
                        options = QgsVectorFileWriter.SaveVectorOptions()
                        options.driverName = 'GPKG'
                        options.layerName = layer.name()
                        options.fileEncoding = 'UTF-8'
                        options.destCRS = QgsCoordinateReferenceSystem(epsg)
                        options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile
                        error =  QgsVectorFileWriter.writeAsVectorFormatV3(layer, outGPKG, QgsCoordinateTransformContext(), options)
