跳转至

XML编辑器 XmlEditor API

src.components.XmlEditor.XmlEditor.XmlEditor

Bases: QWidget

XML编辑器主类 - 支持多层级结构

Source code in src/components/XmlEditor/XmlEditor.py
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
class XmlEditor(QWidget):
    """XML编辑器主类 - 支持多层级结构"""

    def __init__(self, parent=None):
        """初始化XML编辑器

        Args:
            parent: 父级窗口
        """
        super().__init__(parent)
        self.ui = Ui_XmlEditor()
        self.ui.setupUi(self)
        self.config = XmlEditorConfig()

        # 异步加载相关属性
        self.loading_timer = QTimer()
        self.loading_queue = []
        self.current_loading_item = None
        self.total_elements = 0
        self.processed_elements = 0

        self.setup_tree_editor()
        self.setup_connections()
        self.setup_async_loading()

    def setup_tree_editor(self):
        """设置树形编辑器"""
        # 移除原有的滚动区域,替换为分割器
        self.ui.verticalLayout.removeWidget(self.ui.scrollArea)
        self.ui.scrollArea.setParent(None)

        # 创建分割器
        splitter = QSplitter(Qt.Horizontal)

        # 左侧:树形编辑器
        left_widget = self._create_tree_widget()

        # 右侧:实时预览
        right_widget = self._create_preview_widget()

        # 添加到分割器
        splitter.addWidget(left_widget)
        splitter.addWidget(right_widget)
        splitter.setSizes(self.config.SPLITTER_SIZES)

        # 添加到主布局
        self.ui.verticalLayout.insertWidget(1, splitter)

        # 初始更新预览
        self.update_preview()

    def _create_tree_widget(self) -> QWidget:
        """创建树形编辑器部件

        Returns:
            QWidget: 树形编辑器部件
        """
        widget = QWidget()
        layout = QVBoxLayout(widget)

        # 树形编辑器标题
        title = QLabel("XML结构编辑器")
        title.setFont(self.config.TITLE_FONT)
        layout.addWidget(title)

        # 创建树形控件
        self.xml_tree = XmlTreeWidget()
        self.xml_tree.itemChanged.connect(self.on_tree_changed)
        layout.addWidget(self.xml_tree)

        return widget

    def _create_preview_widget(self) -> QWidget:
        """创建预览部件

        Returns:
            QWidget: 预览部件
        """
        widget = QWidget()
        layout = QVBoxLayout(widget)

        # 预览标题
        title = QLabel("实时预览")
        title.setFont(self.config.TITLE_FONT)
        layout.addWidget(title)

        # 添加进度条和状态标签
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        self.progress_bar.setTextVisible(True)
        layout.addWidget(self.progress_bar)

        self.status_label = QLabel("")
        self.status_label.setVisible(False)
        layout.addWidget(self.status_label)

        # 预览文本框
        self.preview_text = QTextEdit()
        self.preview_text.setReadOnly(True)
        self.preview_text.setFont(self.config.PREVIEW_FONT)
        layout.addWidget(self.preview_text)

        return widget

    def setup_connections(self):
        """设置信号连接"""
        self.ui.btn_add_element.clicked.connect(self.add_root_element)
        self.ui.btn_clear_all.clicked.connect(self.clear_all_elements)
        self.ui.btn_preview.clicked.connect(self.preview_xml)
        self.ui.btn_download.clicked.connect(self.download_xml)

        # 新增:加载XML按钮
        self.btn_load = QPushButton("加载XML")
        # 插入到顶部工具栏,在"添加元素"按钮之前
        try:
            self.ui.horizontalLayout_top.insertWidget(2, self.btn_load)
        except Exception:
            # 如果插入失败则追加
            self.ui.horizontalLayout_top.addWidget(self.btn_load)
        self.btn_load.clicked.connect(self.open_xml_file)

        # 修改按钮文本
        self.ui.btn_add_element.setText("添加根级元素")

    def setup_async_loading(self):
        """设置异步加载机制"""
        self.loading_timer.timeout.connect(self.process_loading_batch)
        self.loading_timer.setSingleShot(False)

    def open_xml_file(self):
        """打开并加载XML文件"""
        try:
            # 选择文件
            file_path, _ = QFileDialog.getOpenFileName(
                self, "选择XML文件", "", "XML文件 (*.xml);;所有文件 (*)"
            )

            if not file_path:
                return

            # 读取文件内容
            try:
                # 优先尝试UTF-8编码
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
            except UnicodeDecodeError:
                # 如果UTF-8失败,尝试其他编码
                try:
                    with open(file_path, 'r', encoding='gbk') as f:
                        content = f.read()
                except UnicodeDecodeError:
                    with open(file_path, 'r', encoding='latin-1') as f:
                        content = f.read()

            # 解析XML内容
            root = ET.fromstring(content)

            # 使用异步加载机制
            self.start_async_loading(root)

        except ET.ParseError as e:
            QMessageBox.critical(self, "XML解析错误", f"XML文件格式错误: {str(e)}")
        except FileNotFoundError:
            QMessageBox.critical(self, "文件错误", "找不到指定的文件")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"加载XML文件时发生错误: {str(e)}")

    def start_async_loading(self, root_element):
        """开始异步加载XML数据

        Args:
            root_element: 要加载的根XML元素
        """
        try:
            # 停止之前的加载
            if self.loading_timer.isActive():
                self.loading_timer.stop()

            # 清空现有数据
            self.xml_tree.clear()
            self.xml_tree.add_root_item()

            # 计算总元素数量
            self.total_elements = self._count_elements(root_element)
            self.processed_elements = 0

            # 准备加载队列
            self.loading_queue = []
            root_item = self.xml_tree.topLevelItem(0)
            if isinstance(root_item, XmlTreeItem):
                self._prepare_loading_queue(root_element, root_item)

            # 显示进度条和状态
            self.progress_bar.setVisible(True)
            self.progress_bar.setMaximum(self.total_elements)
            self.progress_bar.setValue(0)
            self.status_label.setVisible(True)
            self.status_label.setText(f"正在加载XML文件... (0/{self.total_elements})")

            # 禁用加载按钮
            self.btn_load.setEnabled(False)

            # 开始定时器
            self.loading_timer.start(10)  # 每10ms处理一批

        except Exception as e:
            QMessageBox.critical(self, "错误", f"准备加载时发生错误:{str(e)}")
            self._finish_loading()

    def _count_elements(self, element):
        """递归计算XML元素总数

        Args:
            element: 要计算的XML元素

        Returns:
            int: 元素总数
        """
        count = 1
        for child in element:
            count += self._count_elements(child)
        return count

    def _prepare_loading_queue(self, xml_element, tree_item):
        """准备加载队列

        Args:
            xml_element: 要加载的XML元素
            tree_item: 对应的树形项
        """
        # 添加当前元素到队列
        self.loading_queue.append((xml_element, tree_item, 'self'))

        # 添加子元素到队列
        for child_element in xml_element:
            child_item = XmlTreeItem(tree_item)
            tree_item.addChild(child_item)
            self.loading_queue.append((child_element, child_item, 'child'))
            # 递归添加子元素的子元素
            self._prepare_loading_queue(child_element, child_item)

    def process_loading_batch(self):
        """处理一批加载任务"""
        batch_size = 5  # 每批处理5个元素
        processed_in_batch = 0

        while self.loading_queue and processed_in_batch < batch_size:
            xml_element, tree_item, load_type = self.loading_queue.pop(0)

            try:
                if load_type == 'self':
                    # 加载元素自身的属性和文本
                    tree_item.tag_name = xml_element.tag
                    tree_item.tag_edit.setText(tree_item.tag_name)

                    # 加载属性
                    tree_item.attributes = dict(xml_element.attrib)
                    tree_item.update_attributes_display()

                    # 加载文本内容
                    if xml_element.text and xml_element.text.strip():
                        tree_item.text_content = xml_element.text.strip()
                        tree_item.text_edit.setText(tree_item.text_content)

                self.processed_elements += 1
                processed_in_batch += 1

                # 更新进度
                self.progress_bar.setValue(self.processed_elements)
                self.status_label.setText(
                    f"正在加载XML文件... ({self.processed_elements}/{self.total_elements}) - {xml_element.tag}"
                )

            except Exception as e:
                print(f"加载元素时出错: {e}")
                self.processed_elements += 1

        # 检查是否完成
        if not self.loading_queue:
            self._finish_loading()

    def _finish_loading(self):
        """完成加载"""
        self.loading_timer.stop()
        self.progress_bar.setVisible(False)
        self.status_label.setVisible(False)
        self.btn_load.setEnabled(True)

        # 展开根节点
        if self.xml_tree.topLevelItemCount() > 0:
            root_item = self.xml_tree.topLevelItem(0)
            self.xml_tree.expandItem(root_item)

        # 更新预览
        self.update_preview()

        QMessageBox.information(self, "成功", f"XML文件加载完成!共处理 {self.processed_elements} 个元素。")

    def on_tree_changed(self):
        """树形结构改变时更新预览"""
        self.update_preview()

    def update_preview(self):
        """更新实时预览"""
        xml_element = self.xml_tree.get_xml_data()
        formatted_xml = XmlUtils.format_xml(xml_element)
        self.preview_text.setPlainText(formatted_xml)

    def add_root_element(self):
        """添加根级元素"""
        if self.xml_tree.topLevelItemCount() > 0:
            root_item = self.xml_tree.topLevelItem(0)
            if isinstance(root_item, XmlTreeItem):
                root_item.add_child_item()

    def clear_all_elements(self):
        """清空所有元素"""
        reply = QMessageBox.question(
            self, "确认", "确定要清空所有元素吗?",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No
        )

        if reply == QMessageBox.Yes:
            self.xml_tree.clear()
            self.xml_tree.add_root_item()
            self.update_preview()

    def get_xml_data(self):
        """获取当前的XML数据

        Returns:
            ET.Element or None: 当前XML数据或None
        """
        return self.xml_tree.get_xml_data()

    def load_xml_data(self, xml_element):
        """加载XML数据 - 已弃用,使用start_async_loading替代

        Args:
            xml_element: 要加载的XML元素
        """
        # 重定向到异步加载方法
        self.start_async_loading(xml_element)

    def preview_xml(self):
        """预览XML"""
        try:
            xml_element = self.get_xml_data()
            if xml_element is None:
                QMessageBox.information(self, "提示", "没有有效的XML数据可预览!")
                return

            preview_dialog = XmlPreviewDialog(xml_element, self)
            preview_dialog.exec_()

        except Exception as e:
            QMessageBox.critical(self, "错误", f"预览XML时发生错误: {str(e)}")

    def download_xml(self):
        """下载XML文件"""
        try:
            xml_element = self.get_xml_data()
            if xml_element is None:
                QMessageBox.information(self, "提示", "没有有效的XML数据可下载!")
                return

            # 选择保存路径
            file_path, _ = QFileDialog.getSaveFileName(
                self, "保存XML文件", "data.xml", "XML文件 (*.xml)"
            )

            if file_path:
                XmlUtils.save_xml_to_file(xml_element, file_path)
                QMessageBox.information(self, "成功", f"XML文件已保存到: {file_path}")

        except Exception as e:
            QMessageBox.critical(self, "错误", f"保存XML文件时发生错误: {str(e)}")

__init__(parent=None)

初始化XML编辑器

Parameters:

Name Type Description Default
parent

父级窗口

None
Source code in src/components/XmlEditor/XmlEditor.py
def __init__(self, parent=None):
    """初始化XML编辑器

    Args:
        parent: 父级窗口
    """
    super().__init__(parent)
    self.ui = Ui_XmlEditor()
    self.ui.setupUi(self)
    self.config = XmlEditorConfig()

    # 异步加载相关属性
    self.loading_timer = QTimer()
    self.loading_queue = []
    self.current_loading_item = None
    self.total_elements = 0
    self.processed_elements = 0

    self.setup_tree_editor()
    self.setup_connections()
    self.setup_async_loading()

add_root_element()

添加根级元素

Source code in src/components/XmlEditor/XmlEditor.py
def add_root_element(self):
    """添加根级元素"""
    if self.xml_tree.topLevelItemCount() > 0:
        root_item = self.xml_tree.topLevelItem(0)
        if isinstance(root_item, XmlTreeItem):
            root_item.add_child_item()

clear_all_elements()

清空所有元素

Source code in src/components/XmlEditor/XmlEditor.py
def clear_all_elements(self):
    """清空所有元素"""
    reply = QMessageBox.question(
        self, "确认", "确定要清空所有元素吗?",
        QMessageBox.Yes | QMessageBox.No,
        QMessageBox.No
    )

    if reply == QMessageBox.Yes:
        self.xml_tree.clear()
        self.xml_tree.add_root_item()
        self.update_preview()

download_xml()

下载XML文件

Source code in src/components/XmlEditor/XmlEditor.py
def download_xml(self):
    """下载XML文件"""
    try:
        xml_element = self.get_xml_data()
        if xml_element is None:
            QMessageBox.information(self, "提示", "没有有效的XML数据可下载!")
            return

        # 选择保存路径
        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存XML文件", "data.xml", "XML文件 (*.xml)"
        )

        if file_path:
            XmlUtils.save_xml_to_file(xml_element, file_path)
            QMessageBox.information(self, "成功", f"XML文件已保存到: {file_path}")

    except Exception as e:
        QMessageBox.critical(self, "错误", f"保存XML文件时发生错误: {str(e)}")

get_xml_data()

获取当前的XML数据

Returns:

Type Description

ET.Element or None: 当前XML数据或None

Source code in src/components/XmlEditor/XmlEditor.py
def get_xml_data(self):
    """获取当前的XML数据

    Returns:
        ET.Element or None: 当前XML数据或None
    """
    return self.xml_tree.get_xml_data()

load_xml_data(xml_element)

加载XML数据 - 已弃用,使用start_async_loading替代

Parameters:

Name Type Description Default
xml_element

要加载的XML元素

required
Source code in src/components/XmlEditor/XmlEditor.py
def load_xml_data(self, xml_element):
    """加载XML数据 - 已弃用,使用start_async_loading替代

    Args:
        xml_element: 要加载的XML元素
    """
    # 重定向到异步加载方法
    self.start_async_loading(xml_element)

on_tree_changed()

树形结构改变时更新预览

Source code in src/components/XmlEditor/XmlEditor.py
def on_tree_changed(self):
    """树形结构改变时更新预览"""
    self.update_preview()

open_xml_file()

打开并加载XML文件

Source code in src/components/XmlEditor/XmlEditor.py
def open_xml_file(self):
    """打开并加载XML文件"""
    try:
        # 选择文件
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择XML文件", "", "XML文件 (*.xml);;所有文件 (*)"
        )

        if not file_path:
            return

        # 读取文件内容
        try:
            # 优先尝试UTF-8编码
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
        except UnicodeDecodeError:
            # 如果UTF-8失败,尝试其他编码
            try:
                with open(file_path, 'r', encoding='gbk') as f:
                    content = f.read()
            except UnicodeDecodeError:
                with open(file_path, 'r', encoding='latin-1') as f:
                    content = f.read()

        # 解析XML内容
        root = ET.fromstring(content)

        # 使用异步加载机制
        self.start_async_loading(root)

    except ET.ParseError as e:
        QMessageBox.critical(self, "XML解析错误", f"XML文件格式错误: {str(e)}")
    except FileNotFoundError:
        QMessageBox.critical(self, "文件错误", "找不到指定的文件")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"加载XML文件时发生错误: {str(e)}")

preview_xml()

预览XML

Source code in src/components/XmlEditor/XmlEditor.py
def preview_xml(self):
    """预览XML"""
    try:
        xml_element = self.get_xml_data()
        if xml_element is None:
            QMessageBox.information(self, "提示", "没有有效的XML数据可预览!")
            return

        preview_dialog = XmlPreviewDialog(xml_element, self)
        preview_dialog.exec_()

    except Exception as e:
        QMessageBox.critical(self, "错误", f"预览XML时发生错误: {str(e)}")

process_loading_batch()

处理一批加载任务

Source code in src/components/XmlEditor/XmlEditor.py
def process_loading_batch(self):
    """处理一批加载任务"""
    batch_size = 5  # 每批处理5个元素
    processed_in_batch = 0

    while self.loading_queue and processed_in_batch < batch_size:
        xml_element, tree_item, load_type = self.loading_queue.pop(0)

        try:
            if load_type == 'self':
                # 加载元素自身的属性和文本
                tree_item.tag_name = xml_element.tag
                tree_item.tag_edit.setText(tree_item.tag_name)

                # 加载属性
                tree_item.attributes = dict(xml_element.attrib)
                tree_item.update_attributes_display()

                # 加载文本内容
                if xml_element.text and xml_element.text.strip():
                    tree_item.text_content = xml_element.text.strip()
                    tree_item.text_edit.setText(tree_item.text_content)

            self.processed_elements += 1
            processed_in_batch += 1

            # 更新进度
            self.progress_bar.setValue(self.processed_elements)
            self.status_label.setText(
                f"正在加载XML文件... ({self.processed_elements}/{self.total_elements}) - {xml_element.tag}"
            )

        except Exception as e:
            print(f"加载元素时出错: {e}")
            self.processed_elements += 1

    # 检查是否完成
    if not self.loading_queue:
        self._finish_loading()

setup_async_loading()

设置异步加载机制

Source code in src/components/XmlEditor/XmlEditor.py
def setup_async_loading(self):
    """设置异步加载机制"""
    self.loading_timer.timeout.connect(self.process_loading_batch)
    self.loading_timer.setSingleShot(False)

setup_connections()

设置信号连接

Source code in src/components/XmlEditor/XmlEditor.py
def setup_connections(self):
    """设置信号连接"""
    self.ui.btn_add_element.clicked.connect(self.add_root_element)
    self.ui.btn_clear_all.clicked.connect(self.clear_all_elements)
    self.ui.btn_preview.clicked.connect(self.preview_xml)
    self.ui.btn_download.clicked.connect(self.download_xml)

    # 新增:加载XML按钮
    self.btn_load = QPushButton("加载XML")
    # 插入到顶部工具栏,在"添加元素"按钮之前
    try:
        self.ui.horizontalLayout_top.insertWidget(2, self.btn_load)
    except Exception:
        # 如果插入失败则追加
        self.ui.horizontalLayout_top.addWidget(self.btn_load)
    self.btn_load.clicked.connect(self.open_xml_file)

    # 修改按钮文本
    self.ui.btn_add_element.setText("添加根级元素")

setup_tree_editor()

设置树形编辑器

Source code in src/components/XmlEditor/XmlEditor.py
def setup_tree_editor(self):
    """设置树形编辑器"""
    # 移除原有的滚动区域,替换为分割器
    self.ui.verticalLayout.removeWidget(self.ui.scrollArea)
    self.ui.scrollArea.setParent(None)

    # 创建分割器
    splitter = QSplitter(Qt.Horizontal)

    # 左侧:树形编辑器
    left_widget = self._create_tree_widget()

    # 右侧:实时预览
    right_widget = self._create_preview_widget()

    # 添加到分割器
    splitter.addWidget(left_widget)
    splitter.addWidget(right_widget)
    splitter.setSizes(self.config.SPLITTER_SIZES)

    # 添加到主布局
    self.ui.verticalLayout.insertWidget(1, splitter)

    # 初始更新预览
    self.update_preview()

start_async_loading(root_element)

开始异步加载XML数据

Parameters:

Name Type Description Default
root_element

要加载的根XML元素

required
Source code in src/components/XmlEditor/XmlEditor.py
def start_async_loading(self, root_element):
    """开始异步加载XML数据

    Args:
        root_element: 要加载的根XML元素
    """
    try:
        # 停止之前的加载
        if self.loading_timer.isActive():
            self.loading_timer.stop()

        # 清空现有数据
        self.xml_tree.clear()
        self.xml_tree.add_root_item()

        # 计算总元素数量
        self.total_elements = self._count_elements(root_element)
        self.processed_elements = 0

        # 准备加载队列
        self.loading_queue = []
        root_item = self.xml_tree.topLevelItem(0)
        if isinstance(root_item, XmlTreeItem):
            self._prepare_loading_queue(root_element, root_item)

        # 显示进度条和状态
        self.progress_bar.setVisible(True)
        self.progress_bar.setMaximum(self.total_elements)
        self.progress_bar.setValue(0)
        self.status_label.setVisible(True)
        self.status_label.setText(f"正在加载XML文件... (0/{self.total_elements})")

        # 禁用加载按钮
        self.btn_load.setEnabled(False)

        # 开始定时器
        self.loading_timer.start(10)  # 每10ms处理一批

    except Exception as e:
        QMessageBox.critical(self, "错误", f"准备加载时发生错误:{str(e)}")
        self._finish_loading()

update_preview()

更新实时预览

Source code in src/components/XmlEditor/XmlEditor.py
def update_preview(self):
    """更新实时预览"""
    xml_element = self.xml_tree.get_xml_data()
    formatted_xml = XmlUtils.format_xml(xml_element)
    self.preview_text.setPlainText(formatted_xml)