Word快速文本对齐程序开发经验:从需求分析到实现部署

发布于:2025-07-20 ⋅ 阅读:(11) ⋅ 点赞:(0)

在日常办公中,文档排版是一项常见但耗时的工作,尤其是当需要处理大量文本并保持格式一致时。Microsoft Word作为最流行的文档处理软件之一,虽然提供了丰富的排版功能,但在处理复杂的文本对齐需求时,往往需要重复执行多个操作,效率较低。本文将分享一个Word快速文本对齐程序的开发经验,从需求分析、技术选型到实现部署,全面介绍如何使用Python开发一个高效的Word文档处理工具。

目录

  1. 项目背景与需求分析
  2. 技术选型与架构设计
  3. 核心功能实现
  4. 用户界面开发
  5. 性能优化
  6. 测试与质量保证
  7. 部署与分发
  8. 用户反馈与迭代优化
  9. 开发过程中的经验与教训
  10. 总结与展望

项目背景与需求分析

项目起源

这个项目源于一个实际的办公需求:在一家法律事务所,律师们需要处理大量的合同文档,这些文档中包含各种条款、表格和列表,需要保持严格的格式一致性。手动调整这些文档的对齐方式不仅耗时,而且容易出错。因此,我们决定开发一个专门的工具来自动化这一过程。

需求分析

通过与用户的深入交流,我们确定了以下核心需求:

  1. 多种对齐方式支持:能够快速应用左对齐、右对齐、居中对齐和两端对齐等多种对齐方式
  2. 批量处理能力:支持同时处理多个段落或整个文档
  3. 选择性对齐:能够根据文本特征(如标题、正文、列表等)选择性地应用不同的对齐方式
  4. 格式保留:在调整对齐方式时保留原有的字体、颜色、大小等格式
  5. 撤销功能:提供操作撤销机制,以防误操作
  6. 用户友好界面:简洁直观的界面,减少学习成本
  7. Word版本兼容性:支持主流的Word版本(2010及以上)

用户场景分析

我们分析了几个典型的用户场景:

  1. 场景一:律师需要将一份50页的合同中所有正文段落设置为两端对齐,而保持标题居中对齐
  2. 场景二:助理需要将多份报告中的表格内容统一设置为右对齐
  3. 场景三:编辑需要快速调整文档中的多级列表,使其保持一致的缩进和对齐方式

这些场景帮助我们更好地理解了用户的实际需求和痛点,为后续的功能设计提供了指导。

技术选型与架构设计

技术选型

在技术选型阶段,我们考虑了多种可能的方案,最终决定使用以下技术栈:

  1. 编程语言:Python,因其丰富的库支持和较低的开发门槛
  2. Word交互:python-docx库,用于读取和修改Word文档
  3. COM接口:pywin32库,用于直接与Word应用程序交互,实现更复杂的操作
  4. 用户界面:PyQt5,用于构建跨平台的图形用户界面
  5. 打包工具:PyInstaller,将Python应用打包为独立的可执行文件

选择这些技术的主要考虑因素包括:

  • 开发效率:Python生态系统提供了丰富的库和工具,可以加速开发过程
  • 跨平台能力:PyQt5可以在Windows、macOS和Linux上运行,虽然我们的主要目标是Windows用户
  • 与Word的兼容性:python-docx和pywin32提供了良好的Word文档操作能力
  • 部署便捷性:PyInstaller可以将应用打包为单个可执行文件,简化分发过程

架构设计

我们采用了经典的MVC(Model-View-Controller)架构设计:

  1. Model(模型层):负责Word文档的读取、分析和修改,包括段落识别、格式应用等核心功能
  2. View(视图层):负责用户界面的展示,包括工具栏、菜单、对话框等
  3. Controller(控制层):负责连接模型层和视图层,处理用户输入并调用相应的模型层功能

此外,我们还设计了以下几个关键模块:

  1. 文档分析器:分析Word文档的结构,识别不同类型的文本元素(标题、正文、列表等)
  2. 格式应用器:应用各种对齐方式和格式设置
  3. 历史记录管理器:记录操作历史,支持撤销功能
  4. 配置管理器:管理用户配置和偏好设置
  5. 插件系统:支持功能扩展,以适应未来可能的需求变化

数据流设计

数据在系统中的流动路径如下:

  1. 用户通过界面选择文档和对齐选项
  2. 控制器接收用户输入,调用文档分析器分析文档结构
  3. 根据分析结果和用户选择,控制器调用格式应用器执行对齐操作
  4. 操作结果反馈给用户,并记录在历史记录中
  5. 用户可以通过界面查看结果,并根据需要撤销操作

核心功能实现

文档读取与分析

首先,我们需要实现文档的读取和分析功能。这里我们使用python-docx库来处理Word文档:

from docx import Document
import re

class DocumentAnalyzer:
    def __init__(self, file_path):
        """
        初始化文档分析器
        
        Args:
            file_path: Word文档路径
        """
        self.document = Document(file_path)
        self.file_path = file_path
        self.paragraphs = self.document.paragraphs
        self.tables = self.document.tables
    
    def analyze_structure(self):
        """
        分析文档结构,识别不同类型的文本元素
        
        Returns:
            文档结构信息
        """
        structure = {
            'headings': [],
            'normal_paragraphs': [],
            'lists': [],
            'tables': []
        }
        
        # 分析段落
        for i, para in enumerate(self.paragraphs):
            # 检查段落是否为标题
            if para.style.name.startswith('Heading'):
                structure['headings'].append({
                    'index': i,
                    'text': para.text,
                    'level': int(para.style.name.replace('Heading ', '')) if para.style.name != 'Heading' else 1
                })
            # 检查段落是否为列表项
            elif self._is_list_item(para):
                structure['lists'].append({
                    'index': i,
                    'text': para.text,
                    'level': self._get_list_level(para)
                })
            # 普通段落
            else:
                structure['normal_paragraphs'].append({
                    'index': i,
                    'text': para.text
                })
        
        # 分析表格
        for i, table in enumerate(self.tables):
            table_data = []
            for row in table.rows:
                row_data = [cell.text for cell in row.cells]
                table_data.append(row_data)
            
            structure['tables'].append({
                'index': i,
                'data': table_data
            })
        
        return structure
    
    def _is_list_item(self, paragraph):
        """
        判断段落是否为列表项
        
        Args:
            paragraph: 段落对象
            
        Returns:
            是否为列表项
        """
        # 检查段落样式
        if paragraph.style.name.startswith('List'):
            return True
        
        # 检查段落文本特征
        list_patterns = [
            r'^\d+\.\s',  # 数字列表,如"1. "
            r'^[a-zA-Z]\.\s',  # 字母列表,如"a. "
            r'^[\u2022\u2023\u25E6\u2043\u2219]\s',  # 项目符号,如"• "
            r'^[-*]\s'  # 常见的项目符号,如"- "或"* "
        ]
        
        for pattern in list_patterns:
            if re.match(pattern, paragraph.text):
                return True
        
        return False
    
    def _get_list_level(self, paragraph):
        """
        获取列表项的级别
        
        Args:
            paragraph: 段落对象
            
        Returns:
            列表级别
        """
        # 根据缩进判断级别
        indent = paragraph.paragraph_format.left_indent
        if indent is None:
            return 1
        
        # 缩进值转换为级别(每级缩进约为0.5英寸)
        level = int(indent.pt / 36) + 1
        return max(1, min(level, 9))  # 限制在1-9之间

格式应用

接下来,我们实现格式应用功能,包括各种对齐方式的应用:

from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Pt

class FormatApplier:
    def __init__(self, document):
        """
        初始化格式应用器
        
        Args:
            document: Word文档对象
        """
        self.document = document
    
    def apply_alignment(self, paragraph_indices, alignment):
        """
        应用对齐方式
        
        Args:
            paragraph_indices: 段落索引列表
            alignment: 对齐方式
            
        Returns:
            成功应用的段落数量
        """
        alignment_map = {
            'left': WD_ALIGN_PARAGRAPH.LEFT,
            'center': WD_ALIGN_PARAGRAPH.CENTER,
            'right': WD_ALIGN_PARAGRAPH.RIGHT,
            'justify': WD_ALIGN_PARAGRAPH.JUSTIFY
        }
        
        if alignment not in alignment_map:
            raise ValueError(f"不支持的对齐方式: {alignment}")
        
        count = 0
        for idx in paragraph_indices:
            if 0 <= idx < len(self.document.paragraphs):
                self.document.paragraphs[idx].alignment = alignment_map[alignment]
                count += 1
        
        return count
    
    def apply_alignment_to_type(self, structure, element_type, alignment):
        """
        对指定类型的元素应用对齐方式
        
        Args:
            structure: 文档结构信息
            element_type: 元素类型('headings', 'normal_paragraphs', 'lists')
            alignment: 对齐方式
            
        Returns:
            成功应用的元素数量
        """
        if element_type not in structure:
            raise ValueError(f"不支持的元素类型: {element_type}")
        
        indices = [item['index'] for item in structure[element_type]]
        return self.apply_alignment(indices, alignment)
    
    def apply_table_alignment(self, table_index, row_indices=None, col_indices=None, alignment='left'):
        """
        对表格单元格应用对齐方式
        
        Args:
            table_index: 表格索引
            row_indices: 行索引列表,None表示所有行
            col_indices: 列索引列表,None表示所有列
            alignment: 对齐方式
            
        Returns:
            成功应用的单元格数量
        """
        alignment_map = {
            'left': WD_ALIGN_PARAGRAPH.LEFT,
            'center': WD_ALIGN_PARAGRAPH.CENTER,
            'right': WD_ALIGN_PARAGRAPH.RIGHT,
            'justify': WD_ALIGN_PARAGRAPH.JUSTIFY
        }
        
        if alignment not in alignment_map:
            raise ValueError(f"不支持的对齐方式: {alignment}")
        
        if table_index < 0 or table_index >= len(self.document.tables):
            raise ValueError(f"表格索引超出范围: {table_index}")
        
        table = self.document.tables[table_index]
        
        # 确定要处理的行和列
        if row_indices is None:
            row_indices = range(len(table.rows))
        if col_indices is None:
            col_indices = range(len(table.columns))
        
        count = 0
        for row_idx in row_indices:
            if 0 <= row_idx < len(table.rows):
                row = table.rows[row_idx]
                for col_idx in col_indices:
                    if 0 <= col_idx < len(row.cells):
                        cell = row.cells[col_idx]
                        for paragraph in cell.paragraphs:
                            paragraph.alignment = alignment_map[alignment]
                            count += 1
        
        return count
    
    def save(self, file_path=None):
        """
        保存文档
        
        Args:
            file_path: 保存路径,None表示覆盖原文件
            
        Returns:
            保存路径
        """
        save_path = file_path or self.document._path
        self.document.save(save_path)
        return save_path

使用COM接口实现高级功能

对于一些python-docx库无法直接支持的高级功能,我们使用pywin32库通过COM接口直接与Word应用程序交互:

import win32com.client
import os

class WordCOMHandler:
    def __init__(self):
        """初始化Word COM处理器"""
        self.word_app = None
        self.document = None
    
    def open_document(self, file_path):
        """
        打开Word文档
        
        Args:
            file_path: 文档路径
            
        Returns:
            是否成功打开
        """
        try:
            # 获取Word应用程序实例
            self.word_app = win32com.client.Dispatch("Word.Application")
            self.word_app.Visible = False  # 设置为不可见
            
            # 打开文档
            abs_path = os.path.abspath(file_path)
            self.document = self.word_app.Documents.Open(abs_path)
            return True
        
        except Exception as e:
            print(f"打开文档时出错: {e}")
            self.close()
            return False
    
    def apply_special_alignment(self, selection_type, alignment):
        """
        应用特殊对齐方式
        
        Args:
            selection_type: 选择类型('all', 'current_section', 'selection')
            alignment: 对齐方式
            
        Returns:
            是否成功应用
        """
        if not self.document:
            return False
        
        try:
            alignment_map = {
                'left': 0,  # wdAlignParagraphLeft
                'center': 1,  # wdAlignParagraphCenter
                'right': 2,  # wdAlignParagraphRight
                'justify': 3,  # wdAlignParagraphJustify
                'distribute': 4  # wdAlignParagraphDistribute
            }
            
            if alignment not in alignment_map:
                raise ValueError(f"不支持的对齐方式: {alignment}")
            
            # 根据选择类型设置选区
            if selection_type == 'all':
                self.word_app.Selection.WholeStory()
            elif selection_type == 'current_section':
                self.word_app.Selection.Sections(1).Range.Select()
            # 'selection'类型不需要额外操作,使用当前选区
            
            # 应用对齐方式
            self.word_app.Selection.ParagraphFormat.Alignment = alignment_map[alignment]
            return True
        
        except Exception as e:
            print(f"应用对齐方式时出错: {e}")
            return False
    
    def apply_alignment_to_tables(self, alignment):
        """
        对所有表格应用对齐方式
        
        Args:
            alignment: 对齐方式
            
        Returns:
            成功应用的表格数量
        """
        if not self.document:
            return 0
        
        try:
            alignment_map = {
                'left': 0,  # wdAlignParagraphLeft
                'center': 1,  # wdAlignParagraphCenter
                'right': 2,  # wdAlignParagraphRight
                'justify': 3,  # wdAlignParagraphJustify
                'distribute': 4  # wdAlignParagraphDistribute
            }
            
            if alignment not in alignment_map:
                raise ValueError(f"不支持的对齐方式: {alignment}")
            
            count = 0
            for i in range(1, self.document.Tables.Count + 1):
                table = self.document.Tables(i)
                table.Range.ParagraphFormat.Alignment = alignment_map[alignment]
                count += 1
            
            return count
        
        except Exception as e:
            print(f"应用表格对齐方式时出错: {e}")
            return 0
    
    def save_document(self, file_path=None):
        """
        保存文档
        
        Args:
            file_path: 保存路径,None表示覆盖原文件
            
        Returns:
            是否成功保存
        """
        if not self.document:
            return False
        
        try:
            if file_path:
                self.document.SaveAs(file_path)
            else:
                self.document.Save()
            return True
        
        except Exception as e:
            print(f"保存文档时出错: {e}")
            return False
    
    def close(self):
        """关闭文档和Word应用程序"""
        try:
            if self.document:
                self.document.Close(SaveChanges=False)
                self.document = None
            
            if self.word_app:
                self.word_app.Quit()
                self.word_app = None
        
        except Exception as e:
            print(f"关闭Word时出错: {e}")

历史记录管理

为了支持撤销功能,我们实现了一个简单的历史记录管理器:

import os
import shutil
import time

class HistoryManager:
    def __init__(self, max_history=10):
        """
        初始化历史记录管理器
        
        Args:
            max_history: 最大历史记录数量
        """
        self.max_history = max_history
        self.history = []
        self.current_index = -1
        
        # 创建临时目录
        self.temp_dir = os.path.join(os.environ['TEMP'], 'word_aligner_history')
        if not os.path.exists(self.temp_dir):
            os.makedirs(self.temp_dir)
    
    def add_snapshot(self, file_path):
        """
        添加文档快照
        
        Args:
            file_path: 文档路径
            
        Returns:
            快照ID
        """
        # 生成快照ID
        snapshot_id = f"snapshot_{int(time.time())}_{len(self.history)}"
        snapshot_path = os.path.join(self.temp_dir, f"{snapshot_id}.docx")
        
        # 复制文档
        shutil.copy2(file_path, snapshot_path)
        
        # 如果当前不是最新状态,删除后面的历史记录
        if self.current_index < len(self.history) - 1:
            for i in range(self.current_index + 1, len(self.history)):
                old_snapshot = self.history[i]
                old_path = os.path.join(self.temp_dir, f"{old_snapshot}.docx")
                if os.path.exists(old_path):
                    os.remove(old_path)
            
            self.history = self.history[:self.current_index + 1]
        
        # 添加新快照
        self.history.append(snapshot_id)
        self.current_index = len(self.history) - 1
        
        # 如果历史记录超过最大数量,删除最旧的记录
        if len(self.history) > self.max_history:
            oldest_snapshot = self.history[0]
            oldest_path = os.path.join(self.temp_dir, f"{oldest_snapshot}.docx")
            if os.path.exists(oldest_path):
                os.remove(oldest_path)
            
            self.history = self.history[1:]
            self.current_index -= 1
        
        return snapshot_id
    
    def can_undo(self):
        """
        检查是否可以撤销
        
        Returns:
            是否可以撤销
        """
        return self.current_index > 0
    
    def can_redo(self):
        """
        检查是否可以重做
        
        Returns:
            是否可以重做
        """
        return self.current_index < len(self.history) - 1
    
    def undo(self):
        """
        撤销操作
        
        Returns:
            撤销后的快照路径,如果无法撤销则返回None
        """
        if not self.can_undo():
            return None
        
        self.current_index -= 1
        snapshot_id = self.history[self.current_index]
        snapshot_path = os.path.join(self.temp_dir, f"{snapshot_id}.docx")
        
        if os.path.exists(snapshot_path):
            return snapshot_path
        else:
            return None
    
    def redo(self):
        """
        重做操作
        
        Returns:
            重做后的快照路径,如果无法重做则返回None
        """
        if not self.can_redo():
            return None
        
        self.current_index += 1
        snapshot_id = self.history[self.current_index]
        snapshot_path = os.path.join(self.temp_dir, f"{snapshot_id}.docx")
        
        if os.path.exists(snapshot_path):
            return snapshot_path
        else:
            return None
    
    def cleanup(self):
        """清理临时文件"""
        for snapshot_id in self.history:
            snapshot_path = os.path.join(self.temp_dir, f"{snapshot_id}.docx")
            if os.path.exists(snapshot_path):
                os.remove(snapshot_path)
        
        if os.path.exists(self.temp_dir) and not os.listdir(self.temp_dir):
            os.rmdir(self.temp_dir)

用户界面开发

为了提供良好的用户体验,我们使用PyQt5开发了一个直观的图形用户界面。

主窗口设计

import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                             QHBoxLayout, QPushButton, QLabel, QComboBox, 
                             QFileDialog, QMessageBox, QProgressBar, QAction, 
                             QToolBar, QStatusBar, QGroupBox, QRadioButton)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QIcon

class AlignmentWorker(QThread):
    """后台工作线程,用于处理耗时的对齐操作"""
    finished = pyqtSignal(bool, str)
    progress = pyqtSignal(int)
    
    def __init__(self, file_path, alignment_type, element_type):
        super().__init__()
        self.file_path = file_path
        self.alignment_type = alignment_type
        self.element_type = element_type
    
    def run(self):
        try:
            # 创建文档分析器
            analyzer = DocumentAnalyzer(self.file_path)
            structure = analyzer.analyze_structure()
            
            # 创建格式应用器
            applier = FormatApplier(analyzer.document)
            
            # 应用对齐方式
            count = 0
            total = len(structure[self.element_type]) if self.element_type in structure else 0
            
            for i, item in enumerate(structure.get(self.element_type, [])):
                indices = [item['index']]
                applier.apply_alignment(indices, self.alignment_type)
                count += 1
                self.progress.emit(int(i / total * 100) if total > 0 else 100)
            
            # 保存文档
            applier.save()
            
            self.finished.emit(True, f"成功对 {count} 个元素应用了{self.alignment_type}对齐")
        
        except Exception as e:
            self.finished.emit(False, f"操作失败: {str(e)}")

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()
        
        # 初始化历史记录管理器
        self.history_manager = HistoryManager()
        
        # 当前文档路径
        self.current_file = None
    
    def init_ui(self):
        """初始化用户界面"""
        self.setWindowTitle("Word快速文本对齐工具")
        self.setMinimumSize(600, 400)
        
        # 创建中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 主布局
        main_layout = QVBoxLayout(central_widget)
        
        # 文件选择区域
        file_group = QGroupBox("文档选择")
        file_layout = QHBoxLayout()
        
        self.file_label = QLabel("未选择文件")
        self.file_button = QPushButton("选择Word文档")
        self.file_button.clicked.connect(self.select_file)
        
        file_layout.addWidget(self.file_label, 1)
        file_layout.addWidget(self.file_button)
        file_group.setLayout(file_layout)
        main_layout.addWidget(file_group)
        
        # 对齐选项区域
        alignment_group = QGroupBox("对齐选项")
        alignment_layout = QVBoxLayout()
        
        # 对齐类型
        alignment_type_layout = QHBoxLayout()
        alignment_type_layout.addWidget(QLabel("对齐方式:"))
        
        self.alignment_combo = QComboBox()
        self.alignment_combo.addItems(["左对齐", "居中对齐", "右对齐", "两端对齐"])
        alignment_type_layout.addWidget(self.alignment_combo)
        alignment_layout.addLayout(alignment_type_layout)
        
        # 元素类型
        element_type_layout = QHBoxLayout()
        element_type_layout.addWidget(QLabel("应用于:"))
        
        self.element_combo = QComboBox()
        self.element_combo.addItems(["所有段落", "标题", "正文段落", "列表", "表格"])
        element_type_layout.addWidget(self.element_combo)
        alignment_layout.addLayout(element_type_layout)
        
        alignment_group.setLayout(alignment_layout)
        main_layout.addWidget(alignment_group)
        
        # 操作按钮区域
        button_layout = QHBoxLayout()
        
        self.apply_button = QPushButton("应用对齐")
        self.apply_button.setEnabled(False)
        self.apply_button.clicked.connect(self.apply_alignment)
        
        self.undo_button = QPushButton("撤销")
        self.undo_button.setEnabled(False)
        self.undo_button.clicked.connect(self.undo_action)
        
        self.redo_button = QPushButton("重做")
        self.redo_button.setEnabled(False)
        self.redo_button.clicked.connect(self.redo_action)
        
        button_layout.addWidget(self.apply_button)
        button_layout.addWidget(self.undo_button)
        button_layout.addWidget(self.redo_button)
        main_layout.addLayout(button_layout)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        main_layout.addWidget(self.progress_bar)
        
        # 状态栏
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.statusBar.showMessage("就绪")
        
        # 工具栏
        toolbar = QToolBar("主工具栏")
        self.addToolBar(toolbar)
        
        # 添加工具栏按钮
        open_action = QAction(QIcon.fromTheme("document-open"), "打开", self)
        open_action.triggered.connect(self.select_file)
        toolbar.addAction(open_action)
        
        save_action = QAction(QIcon.fromTheme("document-save"), "保存", self)
        save_action.triggered.connect(self.save_file)
        toolbar.addAction(save_action)
        
        toolbar.addSeparator()
        
        left_action = QAction(QIcon.fromTheme("format-justify-left"), "左对齐", self)
        left_action.triggered.connect(lambda: self.quick_align("left"))
        toolbar.addAction(left_action)
        
        center_action = QAction(QIcon.fromTheme("format-justify-center"), "居中对齐", self)
        center_action.triggered.connect(lambda: self.quick_align("center"))
        toolbar.addAction(center_action)
        
        right_action = QAction(QIcon.fromTheme("format-justify-right"), "右对齐", self)
        right_action.triggered.connect(lambda: self.quick_align("right"))
        toolbar.addAction(right_action)
        
        justify_action = QAction(QIcon.fromTheme("format-justify-fill"), "两端对齐", self)
        justify_action.triggered.connect(lambda: self.quick_align("justify"))
        toolbar.addAction(justify_action)
    
    def select_file(self):
        """选择Word文档"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择Word文档", "", "Word文档 (*.docx *.doc)"
        )
        
        if file_path:
            self.current_file = file_path
            self.file_label.setText(os.path.basename(file_path))
            self.apply_button.setEnabled(True)
            
            # 添加历史记录
            self.history_manager.add_snapshot(file_path)
            self.update_history_buttons()
            
            self.statusBar.showMessage(f"已加载文档: {os.path.basename(file_path)}")
    
    def save_file(self):
        """保存文档"""
        if not self.current_file:
            return
        
        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存Word文档", "", "Word文档 (*.docx)"
        )
        
        if file_path:
            try:
                # 复制当前文件到新位置
                shutil.copy2(self.current_file, file_path)
                self.statusBar.showMessage(f"文档已保存为: {os.path.basename(file_path)}")
            except Exception as e:
                QMessageBox.critical(self, "保存失败", f"保存文档时出错: {str(e)}")
    
    def apply_alignment(self):
        """应用对齐方式"""
        if not self.current_file:
            return
        
        # 获取选项
        alignment_map = {
            "左对齐": "left",
            "居中对齐": "center",
            "右对齐": "right",
            "两端对齐": "justify"
        }
        
        element_map = {
            "所有段落": "all",
            "标题": "headings",
            "正文段落": "normal_paragraphs",
            "列表": "lists",
            "表格": "tables"
        }
        
        alignment_type = alignment_map[self.alignment_combo.currentText()]
        element_type = element_map[self.element_combo.currentText()]
        
        # 添加历史记录
        self.history_manager.add_snapshot(self.current_file)
        
        # 显示进度条
        self.progress_bar.setValue(0)
        self.progress_bar.setVisible(True)
        self.apply_button.setEnabled(False)
        self.statusBar.showMessage("正在应用对齐方式...")
        
        # 创建工作线程
        self.worker = AlignmentWorker(self.current_file, alignment_type, element_type)
        self.worker.progress.connect(self.update_progress)
        self.worker.finished.connect(self.on_alignment_finished)
        self.worker.start()
    
    def update_progress(self, value):
        """更新进度条"""
        self.progress_bar.setValue(value)
    
    def on_alignment_finished(self, success, message):
        """对齐操作完成回调"""
        self.progress_bar.setVisible(False)
        self.apply_button.setEnabled(True)
        
        if success:
            self.statusBar.showMessage(message)
        else:
            QMessageBox.critical(self, "操作失败", message)
            self.statusBar.showMessage("操作失败")
        
        self.update_history_buttons()
    
    def quick_align(self, alignment):
        """快速应用对齐方式"""
        if not self.current_file:
            return
        
        # 设置对齐方式
        alignment_index = {
            "left": 0,
            "center": 1,
            "right": 2,
            "justify": 3
        }
        
        self.alignment_combo.setCurrentIndex(alignment_index[alignment])
        
        # 应用对齐
        self.apply_alignment()
    
    def update_history_buttons(self):
        """更新历史按钮状态"""
        self.undo_button.setEnabled(self.history_manager.can_undo())
        self.redo_button.setEnabled(self.history_manager.can_redo())
    
    def undo_action(self):
        """撤销操作"""
        snapshot_path = self.history_manager.undo()
        if snapshot_path:
            self.current_file = snapshot_path
            self.statusBar.showMessage("已撤销上一次操作")
            self.update_history_buttons()
    
    def redo_action(self):
        """重做操作"""
        snapshot_path = self.history_manager.redo()
        if snapshot_path:
            self.current_file = snapshot_path
            self.statusBar.showMessage("已重做操作")
            self.update_history_buttons()
    
    def closeEvent(self, event):
        """窗口关闭事件"""
        # 清理临时文件
        self.history_manager.cleanup()
        event.accept()

应用程序入口

def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

性能优化

在开发过程中,我们发现了一些性能瓶颈,并采取了相应的优化措施。

文档加载优化

对于大型文档,加载和分析可能会很耗时。我们采用了以下优化策略:

def optimize_document_loading(file_path):
    """
    优化文档加载过程
    
    Args:
        file_path: 文档路径
        
    Returns:
        优化后的Document对象
    """
    # 1. 使用二进制模式打开文件,减少I/O操作
    with open(file_path, 'rb') as f:
        document = Document(f)
    
    return document

批量处理优化

对于批量操作,我们使用了批处理策略,减少文档保存次数:

def batch_apply_alignment(document, paragraph_indices, alignment):
    """
    批量应用对齐方式
    
    Args:
        document: Word文档对象
        paragraph_indices: 段落索引列表
        alignment: 对齐方式
        
    Returns:
        成功应用的段落数量
    """
    alignment_map = {
        'left': WD_ALIGN_PARAGRAPH.LEFT,
        'center': WD_ALIGN_PARAGRAPH.CENTER,
        'right': WD_ALIGN_PARAGRAPH.RIGHT,
        'justify': WD_ALIGN_PARAGRAPH.JUSTIFY
    }
    
    # 一次性应用所有对齐,只保存一次
    count = 0
    for idx in paragraph_indices:
        if 0 <= idx < len(document.paragraphs):
            document.paragraphs[idx].alignment = alignment_map[alignment]
            count += 1
    
    return count

内存使用优化

对于大型文档,内存使用可能会成为问题。我们实现了一个简单的内存监控和优化机制:

import psutil
import gc

def monitor_memory_usage():
    """
    监控内存使用情况
    
    Returns:
        当前内存使用百分比
    """
    process = psutil.Process()
    return process.memory_percent()

def optimize_memory_usage(threshold=80.0):
    """
    优化内存使用
    
    Args:
        threshold: 内存使用阈值(百分比)
        
    Returns:
        是否进行了优化
    """
    usage = monitor_memory_usage()
    
    if usage > threshold:
        # 强制垃圾回收
        gc.collect()
        return True
    
    return False

测试与质量保证

为了确保程序的质量和稳定性,我们进行了全面的测试。

单元测试

import unittest
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH

class TestDocumentAnalyzer(unittest.TestCase):
    def setUp(self):
        # 创建测试文档
        self.doc = Document()
        self.doc.add_heading('Test Heading 1', level=1)
        self.doc.add_paragraph('This is a test paragraph.')
        self.doc.add_paragraph('• This is a list item.')
        
        # 保存测试文档
        self.test_file = 'test_document.docx'
        self.doc.save(self.test_file)
        
        # 创建分析器
        self.analyzer = DocumentAnalyzer(self.test_file)
    
    def tearDown(self):
        # 删除测试文档
        import os
        if os.path.exists(self.test_file):
            os.remove(self.test_file)
    
    def test_analyze_structure(self):
        structure = self.analyzer.analyze_structure()
        
        # 验证结构
        self.assertIn('headings', structure)
        self.assertIn('normal_paragraphs', structure)
        self.assertIn('lists', structure)
        
        # 验证标题
        self.assertEqual(len(structure['headings']), 1)
        self.assertEqual(structure['headings'][0]['text'], 'Test Heading 1')
        
        # 验证正文段落
        self.assertEqual(len(structure['normal_paragraphs']), 1)
        self.assertEqual(structure['normal_paragraphs'][0]['text'], 'This is a test paragraph.')
        
        # 验证列表
        self.assertEqual(len(structure['lists']), 1)
        self.assertEqual(structure['lists'][0]['text'], '• This is a list item.')

class TestFormatApplier(unittest.TestCase):
    def setUp(self):
        # 创建测试文档
        self.doc = Document()
        self.doc.add_paragraph('Paragraph 1')
        self.doc.add_paragraph('Paragraph 2')
        self.doc.add_paragraph('Paragraph 3')
        
        # 保存测试文档
        self.test_file = 'test_format.docx'
        self.doc.save(self.test_file)
        
        # 创建格式应用器
        self.doc = Document(self.test_file)
        self.applier = FormatApplier(self.doc)
    
    def tearDown(self):
        # 删除测试文档
        import os
        if os.path.exists(self.test_file):
            os.remove(self.test_file)
    
    def test_apply_alignment(self):
        # 应用左对齐
        count = self.applier.apply_alignment([0], 'left')
        self.assertEqual(count, 1)
        self.assertEqual(self.doc.paragraphs[0].alignment, WD_ALIGN_PARAGRAPH.LEFT)
        
        # 应用居中对齐
        count = self.applier.apply_alignment([1], 'center')
        self.assertEqual(count, 1)
        self.assertEqual(self.doc.paragraphs[1].alignment, WD_ALIGN_PARAGRAPH.CENTER)
        
        # 应用右对齐
        count = self.applier.apply_alignment([2], 'right')
        self.assertEqual(count, 1)
        self.assertEqual(self.doc.paragraphs[2].alignment, WD_ALIGN_PARAGRAPH.RIGHT)
        
        # 应用无效索引
        count = self.applier.apply_alignment([10], 'left')
        self.assertEqual(count, 0)

# 运行测试
if __name__ == '__main__':
    unittest.main()

集成测试

import unittest
import os
import shutil
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH

class TestIntegration(unittest.TestCase):
    def setUp(self):
        # 创建测试目录
        self.test_dir = 'test_integration'
        if not os.path.exists(self.test_dir):
            os.makedirs(self.test_dir)
        
        # 创建测试文档
        self.doc = Document()
        self.doc.add_heading('Test Document', level=1)
        self.doc.add_paragraph('This is a test paragraph.')
        self.doc.add_paragraph('• This is a list item.')
        
        # 添加表格
        table = self.doc.add_table(rows=2, cols=2)
        table.cell(0, 0).text = 'Cell 1'
        table.cell(0, 1).text = 'Cell 2'
        table.cell(1, 0).text = 'Cell 3'
        table.cell(1, 1).text = 'Cell 4'
        
        # 保存测试文档
        self.test_file = os.path.join(self.test_dir, 'test_document.docx')
        self.doc.save(self.test_file)
    
    def tearDown(self):
        # 删除测试目录
        if os.path.exists(self.test_dir):
            shutil.rmtree(self.test_dir)
    
    def test_end_to_end(self):
        # 1. 分析文档结构
        analyzer = DocumentAnalyzer(self.test_file)
        structure = analyzer.analyze_structure()
        
        # 验证结构
        self.assertIn('headings', structure)
        self.assertIn('normal_paragraphs', structure)
        self.assertIn('lists', structure)
        self.assertIn('tables', structure)
        
        # 2. 应用对齐方式
        applier = FormatApplier(analyzer.document)
        
        # 对标题应用居中对齐
        heading_indices = [item['index'] for item in structure['headings']]
        applier.apply_alignment(heading_indices, 'center')
        
        # 对正文应用两端对齐
        para_indices = [item['index'] for item in structure['normal_paragraphs']]
        applier.apply_alignment(para_indices, 'justify')
        
        # 对列表应用左对齐
        list_indices = [item['index'] for item in structure['lists']]
        applier.apply_alignment(list_indices, 'left')
        
        # 保存修改后的文档
        output_file = os.path.join(self.test_dir, 'output.docx')
        applier.save(output_file)
        
        # 3. 验证结果
        result_doc = Document(output_file)
        
        # 验证标题对齐方式
        for idx in heading_indices:
            self.assertEqual(result_doc.paragraphs[idx].alignment, WD_ALIGN_PARAGRAPH.CENTER)
        
        # 验证正文对齐方式
        for idx in para_indices:
            self.assertEqual(result_doc.paragraphs[idx].alignment, WD_ALIGN_PARAGRAPH.JUSTIFY)
        
        # 验证列表对齐方式
        for idx in list_indices:
            self.assertEqual(result_doc.paragraphs[idx].alignment, WD_ALIGN_PARAGRAPH.LEFT)

# 运行测试
if __name__ == '__main__':
    unittest.main()

性能测试

import time
import os
import matplotlib.pyplot as plt
import numpy as np

def generate_test_document(file_path, num_paragraphs):
    """
    生成测试文档
    
    Args:
        file_path: 文档路径
        num_paragraphs: 段落数量
    """
    doc = Document()
    
    # 添加标题
    doc.add_heading(f'Test Document with {num_paragraphs} paragraphs', level=1)
    
    # 添加段落
    for i in range(num_paragraphs):
        if i % 10 == 0:
            doc.add_heading(f'Section {i//10 + 1}', level=2)
        elif i % 5 == 0:
            doc.add_paragraph(f'• List item {i}')
        else:
            doc.add_paragraph(f'This is paragraph {i}. ' * 5)
    
    # 保存文档
    doc.save(file_path)

def run_performance_test():
    """运行性能测试"""
    test_dir = 'performance_test'
    if not os.path.exists(test_dir):
        os.makedirs(test_dir)
    
    # 测试不同大小的文档
    paragraph_counts = [10, 50, 100, 500, 1000]
    loading_times = []
    analysis_times = []
    alignment_times = []
    
    for count in paragraph_counts:
        # 生成测试文档
        test_file = os.path.join(test_dir, f'test_{count}.docx')
        generate_test_document(test_file, count)
        
        # 测试加载时间
        start_time = time.time()
        doc = optimize_document_loading(test_file)
        loading_time = time.time() - start_time
        loading_times.append(loading_time)
        
        # 测试分析时间
        analyzer = DocumentAnalyzer(test_file)
        start_time = time.time()
        structure = analyzer.analyze_structure()
        analysis_time = time.time() - start_time
        analysis_times.append(analysis_time)
        
        # 测试对齐时间
        applier = FormatApplier(analyzer.document)
        para_indices = [item['index'] for item in structure.get('normal_paragraphs', [])]
        start_time = time.time()
        applier.apply_alignment(para_indices, 'justify')
        alignment_time = time.time() - start_time
        alignment_times.append(alignment_time)
        
        print(f"文档大小: {count} 段落")
        print(f"  加载时间: {loading_time:.4f} 秒")
        print(f"  分析时间: {analysis_time:.4f} 秒")
        print(f"  对齐时间: {alignment_time:.4f} 秒")
    
    # 绘制性能图表
    plt.figure(figsize=(12, 8))
    
    plt.subplot(3, 1, 1)
    plt.plot(paragraph_counts, loading_times, 'o-', label='加载时间')
    plt.xlabel('段落数量')
    plt.ylabel('时间 (秒)')
    plt.title('文档加载性能')
    plt.grid(True)
    plt.legend()
    
    plt.subplot(3, 1, 2)
    plt.plot(paragraph_counts, analysis_times, 'o-', label='分析时间')
    plt.xlabel('段落数量')
    plt.ylabel('时间 (秒)')
    plt.title('文档分析性能')
    plt.grid(True)
    plt.legend()
    
    plt.subplot(3, 1, 3)
    plt.plot(paragraph_counts, alignment_times, 'o-', label='对齐时间')
    plt.xlabel('段落数量')
    plt.ylabel('时间 (秒)')
    plt.title('对齐应用性能')
    plt.grid(True)
    plt.legend()
    
    plt.tight_layout()
    plt.savefig(os.path.join(test_dir, 'performance_results.png'))
    plt.show()
    
    # 清理测试文件
    for count in paragraph_counts:
        test_file = os.path.join(test_dir, f'test_{count}.docx')
        if os.path.exists(test_file):
            os.remove(test_file)

# 运行性能测试
if __name__ == '__main__':
    run_performance_test()

部署与分发

为了方便用户使用,我们需要将程序打包为可执行文件。

使用PyInstaller打包

# setup.py
from setuptools import setup, find_packages

setup(
    name="word_text_aligner",
    version="1.0.0",
    packages=find_packages(),
    install_requires=[
        "python-docx>=0.8.10",
        "pywin32>=227",
        "PyQt5>=5.15.0",
        "matplotlib>=3.3.0",
        "numpy>=1.19.0",
        "psutil>=5.7.0"
    ],
    entry_points={
        'console_scripts': [
            'word_aligner=word_aligner.main:main',
        ],
    },
    author="Your Name",
    author_email="your.email@example.com",
    description="A tool for quick text alignment in Word documents",
    keywords="word, alignment, document, text",
    url="https://github.com/yourusername/word-text-aligner",
    classifiers=[
        "Development Status :: 4 - Beta",
        "Intended Audience :: End Users/Desktop",
        "Programming Language :: Python :: 3",
        "Topic :: Office/Business :: Office Suites",
    ],
)

使用PyInstaller创建可执行文件:

# 安装PyInstaller
pip install pyinstaller

# 创建单文件可执行程序
pyinstaller --onefile --windowed --icon=icon.ico --name=WordTextAligner main.py

# 创建带有所有依赖的目录
pyinstaller --name=WordTextAligner --windowed --icon=icon.ico main.py

创建安装程序

为了提供更好的用户体验,我们可以使用NSIS(Nullsoft Scriptable Install System)创建Windows安装程序:

; word_aligner_installer.nsi

; 定义应用程序名称和版本
!define APPNAME "Word Text Aligner"
!define APPVERSION "1.0.0"
!define COMPANYNAME "Your Company"

; 包含现代UI
!include "MUI2.nsh"

; 设置应用程序信息
Name "${APPNAME}"
OutFile "${APPNAME} Setup ${APPVERSION}.exe"
InstallDir "$PROGRAMFILES\${APPNAME}"
InstallDirRegKey HKLM "Software\${APPNAME}" "Install_Dir"

; 请求管理员权限
RequestExecutionLevel admin

; 界面设置
!define MUI_ABORTWARNING
!define MUI_ICON "icon.ico"
!define MUI_UNICON "icon.ico"

; 安装页面
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH

; 卸载页面
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES

; 语言
!insertmacro MUI_LANGUAGE "SimpChinese"

; 安装部分
Section "安装"
    SetOutPath "$INSTDIR"
    
    ; 添加文件
    File /r "dist\WordTextAligner\*.*"
    
    ; 创建卸载程序
    WriteUninstaller "$INSTDIR\uninstall.exe"
    
    ; 创建开始菜单快捷方式
    CreateDirectory "$SMPROGRAMS\${APPNAME}"
    CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\WordTextAligner.exe"
    CreateShortcut "$SMPROGRAMS\${APPNAME}\卸载 ${APPNAME}.lnk" "$INSTDIR\uninstall.exe"
    
    ; 创建桌面快捷方式
    CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\WordTextAligner.exe"
    
    ; 写入注册表信息
    WriteRegStr HKLM "Software\${APPNAME}" "Install_Dir" "$INSTDIR"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME}"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" '"$INSTDIR\uninstall.exe"'
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayIcon" "$INSTDIR\WordTextAligner.exe,0"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayVersion" "${APPVERSION}"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "Publisher" "${COMPANYNAME}"
SectionEnd

; 卸载部分
Section "Uninstall"
    ; 删除文件和目录
    Delete "$INSTDIR\*.*"
    RMDir /r "$INSTDIR"
    
    ; 删除快捷方式
    Delete "$SMPROGRAMS\${APPNAME}\*.*"
    RMDir "$SMPROGRAMS\${APPNAME}"
    Delete "$DESKTOP\${APPNAME}.lnk"
    
    ; 删除注册表项
    DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
    DeleteRegKey HKLM "Software\${APPNAME}"
SectionEnd

用户反馈与迭代优化

在程序发布后,我们收集了用户反馈并进行了多轮迭代优化。

用户反馈收集

我们通过以下方式收集用户反馈:

  1. 程序内置的反馈功能
  2. 用户调查问卷
  3. 使用数据分析
class FeedbackCollector:
    def __init__(self, app_version):
        """
        初始化反馈收集器
        
        Args:
            app_version: 应用程序版本
        """
        self.app_version = app_version
        self.feedback_data = []
    
    def collect_usage_data(self, action_type, details=None):
        """
        收集使用数据
        
        Args:
            action_type: 操作类型
            details: 操作详情
        """
        data = {
            'timestamp': time.time(),
            'action_type': action_type,
            'details': details or {}
        }
        self.feedback_data.append(data)
    
    def show_feedback_dialog(self, parent=None):
        """
        显示反馈对话框
        
        Args:
            parent: 父窗口
        """
        from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QTextEdit, QPushButton, QHBoxLayout
        
        dialog = QDialog(parent)
        dialog.setWindowTitle("提供反馈")
        layout = QVBoxLayout()
        
        layout.addWidget(QLabel("请告诉我们您对程序的看法:"))
        
        feedback_text = QTextEdit()
        layout.addWidget(feedback_text)
        
        button_layout = QHBoxLayout()
        cancel_button = QPushButton("取消")
        cancel_button.clicked.connect(dialog.reject)
        
        submit_button = QPushButton("提交")
        submit_button.clicked.connect(lambda: self.submit_feedback(feedback_text.toPlainText(), dialog))
        
        button_layout.addWidget(cancel_button)
        button_layout.addWidget(submit_button)
        layout.addLayout(button_layout)
        
        dialog.setLayout(layout)
        dialog.exec_()
    
    def submit_feedback(self, feedback_text, dialog):
        """
        提交反馈
        
        Args:
            feedback_text: 反馈文本
            dialog: 对话框
        """
        if not feedback_text.strip():
            return
        
        # 收集反馈
        feedback_data = {
            'timestamp': time.time(),
            'app_version': self.app_version,
            'feedback': feedback_text
        }
        
        # 在实际应用中,这里可以将反馈发送到服务器
        print(f"收到反馈: {feedback_data}")
        
        # 关闭对话框
        dialog.accept()
    
    def save_feedback_data(self, file_path):
        """
        保存反馈数据
        
        Args:
            file_path: 文件路径
        """
        import json
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(self.feedback_data, f, ensure_ascii=False, indent=2)

迭代优化

根据用户反馈,我们进行了多轮迭代优化,主要包括以下几个方面:

  1. 用户界面改进:简化操作流程,增加更多直观的图标和提示
  2. 功能扩展:增加更多对齐选项和批处理能力
  3. 性能优化:提高大文档处理速度
  4. 稳定性增强:修复各种边缘情况下的崩溃问题
# 版本更新日志
VERSION_HISTORY = [
    {
        'version': '1.0.0',
        'date': '2023-01-15',
        'changes': [
            '初始版本发布',
            '支持基本的文本对齐功能',
            '支持Word 2010及以上版本'
        ]
    },
    {
        'version': '1.1.0',
        'date': '2023-02-28',
        'changes': [
            '添加批量处理功能',
            '优化用户界面',
            '提高大文档处理性能',
            '修复多个稳定性问题'
        ]
    },
    {
        'version': '1.2.0',
        'date': '2023-04-10',
        'changes': [
            '添加表格对齐功能',
            '增加更多对齐选项',
            '添加自定义配置保存功能',
            '改进撤销/重做机制'
        ]
    },
    {
        'version': '1.3.0',
        'date': '2023-06-20',
        'changes': [
            '添加智能对齐建议功能',
            '支持多文档同时处理',
            '添加对齐模板功能',
            '优化内存使用'
        ]
    }
]

开发过程中的经验与教训

在开发这个Word快速文本对齐程序的过程中,我们积累了一些宝贵的经验,也遇到了一些挑战和教训。

技术选型的经验

  1. Python vs. VBA:最初我们考虑使用VBA(Visual Basic for Applications)开发,因为它是Word的原生脚本语言。但考虑到跨平台能力、代码维护性和现代库的支持,我们最终选择了Python。这个决定证明是正确的,因为Python生态系统提供了丰富的工具和库,大大加速了开发过程。

  2. 库的选择:在处理Word文档时,我们比较了多个库,包括python-docx、docx2python和PyWin32。最终我们采用了python-docx作为主要库,并在需要更高级功能时使用PyWin32的COM接口。这种组合提供了良好的平衡:python-docx简单易用,而PyWin32则提供了对Word完整功能的访问。

  3. GUI框架:我们选择PyQt5而不是Tkinter,主要是因为PyQt5提供了更现代的外观和更丰富的组件。虽然学习曲线更陡,但最终产品的用户体验明显更好。

遇到的挑战与解决方案

  1. Word文档格式复杂性:Word文档的内部结构非常复杂,特别是涉及到样式、格式和布局时。我们通过深入研究Word的文档对象模型(DOM)和OOXML格式,逐步掌握了处理这些复杂性的方法。

    def analyze_complex_formatting(paragraph):
        """分析段落的复杂格式"""
        formats = []
        for run in paragraph.runs:
            format_info = {
                'text': run.text,
                'bold': run.bold,
                'italic': run.italic,
                'underline': run.underline,
                'font': run.font.name,
                'size': run.font.size.pt if run.font.size else None,
                'color': run.font.color.rgb if run.font.color else None
            }
            formats.append(format_info)
        return formats
    
  2. 性能问题:在处理大型文档时,我们遇到了严重的性能问题。通过分析,我们发现主要瓶颈在于频繁的文档保存操作和不必要的格式重新计算。

    解决方案:

    • 实现批处理机制,减少保存次数
    • 使用缓存减少重复计算
    • 优化文档加载过程
    def optimize_large_document_processing(document, operations):
        """优化大文档处理"""
        # 禁用屏幕更新以提高性能
        if isinstance(document, win32com.client._dispatch.CDispatch):
            document.Application.ScreenUpdating = False
        
        try:
            # 批量执行操作
            for operation in operations:
                operation_type = operation['type']
                params = operation['params']
                
                if operation_type == 'alignment':
                    apply_alignment_batch(**params)
                elif operation_type == 'formatting':
                    apply_formatting_batch(**params)
                # 其他操作类型...
        
        finally:
            # 恢复屏幕更新
            if isinstance(document, win32com.client._dispatch.CDispatch):
                document.Application.ScreenUpdating = True
    
  3. COM接口的稳定性:使用PyWin32的COM接口时,我们遇到了一些稳定性问题,特别是在长时间运行或处理多个文档时。

    解决方案:

    • 实现错误恢复机制
    • 定期释放COM对象
    • 在关键操作后强制垃圾回收
    def safe_com_operation(func):
        """COM操作安全装饰器"""
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                print(f"COM操作失败: {e}")
                # 尝试释放COM对象
                for arg in args:
                    if isinstance(arg, win32com.client._dispatch.CDispatch):
                        try:
                            arg.Release()
                        except:
                            pass
                # 强制垃圾回收
                gc.collect()
                raise
        return wrapper
    

用户体验设计的教训

  1. 过度设计:最初版本中,我们添加了太多高级功能和选项,导致界面复杂,用户感到困惑。在收到反馈后,我们重新设计了界面,将常用功能放在最前面,将高级选项隐藏在二级菜单中。

  2. 假设用户知识:我们错误地假设用户了解Word排版的专业术语,如"两端对齐"vs"分散对齐"。通过添加简单的图标和预览功能,我们使界面更加直观。

  3. 缺乏反馈:早期版本在执行长时间操作时没有提供足够的进度反馈,用户不知道程序是否仍在工作。我们添加了进度条和状态更新,大大改善了用户体验。

项目管理的经验

  1. 迭代开发:采用迭代开发方法是一个正确的决定。我们首先实现了核心功能,然后根据用户反馈逐步添加新功能和改进。这使我们能够快速交付有用的产品,并根据实际需求进行调整。

  2. 自动化测试:投入时间建立自动化测试框架是值得的。它帮助我们捕获回归问题,并在添加新功能时保持信心。

  3. 文档重要性:良好的文档不仅对用户有帮助,对开发团队也至关重要。我们为每个主要组件和函数编写了详细的文档,这在团队成员变更和后期维护时证明非常有价值。

总结与展望

项目总结

在这个项目中,我们成功开发了一个Word快速文本对齐程序,它能够帮助用户高效地处理文档排版任务。通过使用Python和现代库,我们实现了以下目标:

  1. 高效对齐:用户可以快速应用各种对齐方式,大大提高了排版效率
  2. 智能识别:程序能够智能识别文档中的不同元素类型,如标题、正文和列表
  3. 批量处理:支持同时处理多个段落或整个文档
  4. 用户友好:直观的界面设计,减少了学习成本
  5. 稳定可靠:通过全面测试和错误处理,确保程序在各种情况下都能稳定运行

这个项目不仅满足了最初的需求,还在实际使用中证明了其价值。用户反馈表明,该工具显著提高了文档处理效率,特别是对于需要处理大量文档的专业人士。

未来展望

展望未来,我们计划在以下几个方向继续改进和扩展这个项目:

  1. 智能排版建议:使用机器学习技术分析文档结构和内容,提供智能排版建议
  2. 模板系统:允许用户创建和应用排版模板,进一步提高效率
  3. 云同步:支持云存储和设置同步,使用户可以在多台设备上使用相同的配置
  4. 插件系统:开发插件架构,允许第三方开发者扩展功能
  5. 更多文档格式:扩展支持更多文档格式,如PDF、OpenDocument等
# 未来功能路线图
ROADMAP = [
    {
        'version': '2.0.0',
        'planned_date': '2023-Q4',
        'features': [
            '智能排版建议系统',
            '用户可定义的排版模板',
            '批量文档处理改进'
        ]
    },
    {
        'version': '2.1.0',
        'planned_date': '2024-Q1',
        'features': [
            '云同步功能',
            '用户账户系统',
            '设置和偏好同步'
        ]
    },
    {
        'version': '2.2.0',
        'planned_date': '2024-Q2',
        'features': [
            '插件系统架构',
            'API文档',
            '示例插件'
        ]
    },
    {
        'version': '2.3.0',
        'planned_date': '2024-Q3',
        'features': [
            'PDF文档支持',
            'OpenDocument格式支持',
            '高级格式转换功能'
        ]
    }
]

结语

开发Word快速文本对齐程序是一次充满挑战但也非常有价值的经历。通过这个项目,我们不仅创造了一个有用的工具,还积累了丰富的技术经验和项目管理知识。

最重要的是,这个项目再次证明了自动化工具在提高工作效率方面的巨大潜力。通过将重复性的排版任务自动化,我们帮助用户将时间和精力集中在更有创造性和价值的工作上。

我们相信,随着技术的不断发展和用户需求的演变,这个项目将继续成长和改进,为更多用户提供更好的文档处理体验。

参考资料

  1. python-docx官方文档: https://python-docx.readthedocs.io/
  2. PyWin32文档: https://github.com/mhammond/pywin32
  3. PyQt5教程: https://www.riverbankcomputing.com/static/Docs/PyQt5/
  4. Word文档对象模型参考: https://docs.microsoft.com/en-us/office/vba/api/word.document
  5. OOXML标准: https://www.ecma-international.org/publications-and-standards/standards/ecma-376/
  6. PyInstaller文档: https://pyinstaller.readthedocs.io/
  7. NSIS文档: https://nsis.sourceforge.io/Docs/
  8. Elzer, P. S., & Schwartz, S. (2019). Python GUI Programming with PyQt5. Packt Publishing.
  9. McKinney, W. (2017). Python for Data Analysis. O’Reilly Media.
  10. Hunt, A., & Thomas, D. (2019). The Pragmatic Programmer. Addison-Wesley Professional.

Word快速文本对齐程序开发经验:从需求分析到实现部署

在日常办公中,文档排版是一项常见但耗时的工作,尤其是当需要处理大量文本并保持格式一致时。Microsoft Word作为最流行的文档处理软件之一,虽然提供了丰富的排版功能,但在处理复杂的文本对齐需求时,往往需要重复执行多个操作,效率较低。本文将分享一个Word快速文本对齐程序的开发经验,从需求分析、技术选型到实现部署,全面介绍如何使用Python开发一个高效的Word文档处理工具。

目录

  1. 项目背景与需求分析
  2. 技术选型与架构设计
  3. 核心功能实现
  4. 用户界面开发
  5. 性能优化
  6. 测试与质量保证
  7. 部署与分发
  8. 用户反馈与迭代优化
  9. 开发过程中的经验与教训
  10. 总结与展望

项目背景与需求分析

项目起源

这个项目源于一个实际的办公需求:在一家法律事务所,律师们需要处理大量的合同文档,这些文档中包含各种条款、表格和列表,需要保持严格的格式一致性。手动调整这些文档的对齐方式不仅耗时,而且容易出错。因此,我们决定开发一个专门的工具来自动化这一过程。

需求分析

通过与用户的深入交流,我们确定了以下核心需求:

  1. 多种对齐方式支持:能够快速应用左对齐、右对齐、居中对齐和两端对齐等多种对齐方式
  2. 批量处理能力:支持同时处理多个段落或整个文档
  3. 选择性对齐:能够根据文本特征(如标题、正文、列表等)选择性地应用不同的对齐方式
  4. 格式保留:在调整对齐方式时保留原有的字体、颜色、大小等格式
  5. 撤销功能:提供操作撤销机制,以防误操作
  6. 用户友好界面:简洁直观的界面,减少学习成本
  7. Word版本兼容性:支持主流的Word版本(2010及以上)

用户场景分析

我们分析了几个典型的用户场景:

  1. 场景一:律师需要将一份50页的合同中所有正文段落设置为两端对齐,而保持标题居中对齐
  2. 场景二:助理需要将多份报告中的表格内容统一设置为右对齐
  3. 场景三:编辑需要快速调整文档中的多级列表,使其保持一致的缩进和对齐方式

这些场景帮助我们更好地理解了用户的实际需求和痛点,为后续的功能设计提供了指导。

技术选型与架构设计

技术选型

在技术选型阶段,我们考虑了多种可能的方案,最终决定使用以下技术栈:

  1. 编程语言:Python,因其丰富的库支持和较低的开发门槛
  2. Word交互:python-docx库,用于读取和修改Word文档
  3. COM接口:pywin32库,用于直接与Word应用程序交互,实现更复杂的操作
  4. 用户界面:PyQt5,用于构建跨平台的图形用户界面
  5. 打包工具:PyInstaller,将Python应用打包为独立的可执行文件

选择这些技术的主要考虑因素包括:

  • 开发效率:Python生态系统提供了丰富的库和工具,可以加速开发过程
  • 跨平台能力:PyQt5可以在Windows、macOS和Linux上运行,虽然我们的主要目标是Windows用户
  • 与Word的兼容性:python-docx和pywin32提供了良好的Word文档操作能力
  • 部署便捷性:PyInstaller可以将应用打包为单个可执行文件,简化分发过程

架构设计

我们采用了经典的MVC(Model-View-Controller)架构设计:

  1. Model(模型层):负责Word文档的读取、分析和修改,包括段落识别、格式应用等核心功能
  2. View(视图层):负责用户界面的展示,包括工具栏、菜单、对话框等
  3. Controller(控制层):负责连接模型层和视图层,处理用户输入并调用相应的模型层功能

此外,我们还设计了以下几个关键模块:

  1. 文档分析器:分析Word文档的结构,识别不同类型的文本元素(标题、正文、列表等)
  2. 格式应用器:应用各种对齐方式和格式设置
  3. 历史记录管理器:记录操作历史,支持撤销功能
  4. 配置管理器:管理用户配置和偏好设置
  5. 插件系统:支持功能扩展,以适应未来可能的需求变化

数据流设计

数据在系统中的流动路径如下:

  1. 用户通过界面选择文档和对齐选项
  2. 控制器接收用户输入,调用文档分析器分析文档结构
  3. 根据分析结果和用户选择,控制器调用格式应用器执行对齐操作
  4. 操作结果反馈给用户,并记录在历史记录中
  5. 用户可以通过界面查看结果,并根据需要撤销操作

核心功能实现

文档读取与分析

首先,我们需要实现文档的读取和分析功能。这里我们使用python-docx库来处理Word文档:

from docx import Document
import re

class DocumentAnalyzer:
    def __init__(self, file_path):
        """
        初始化文档分析器
        
        Args:
            file_path: Word文档路径
        """
        self.document = Document(file_path)
        self.file_path = file_path
        self.paragraphs = self.document.paragraphs
        self.tables = self.document.tables
    
    def analyze_structure(self):
        """
        分析文档结构,识别不同类型的文本元素
        
        Returns:
            文档结构信息
        """
        structure = {
            'headings': [],
            'normal_paragraphs': [],
            'lists': [],
            'tables': []
        }
        
        # 分析段落
        for i, para in enumerate(self.paragraphs):
            # 检查段落是否为标题
            if para.style.name.startswith('Heading'):
                structure['headings'].append({
                    'index': i,
                    'text': para.text,
                    'level': int(para.style.name.replace('Heading ', '')) if para.style.name != 'Heading' else 1
                })
            # 检查段落是否为列表项
            elif self._is_list_item(para):
                structure['lists'].append({
                    'index': i,
                    'text': para.text,
                    'level': self._get_list_level(para)
                })
            # 普通段落
            else:
                structure['normal_paragraphs'].append({
                    'index': i,
                    'text': para.text
                })
        
        # 分析表格
        for i, table in enumerate(self.tables):
            table_data = []
            for row in table.rows:
                row_data = [cell.text for cell in row.cells]
                table_data.append(row_data)
            
            structure['tables'].append({
                'index': i,
                'data': table_data
            })
        
        return structure
    
    def _is_list_item(self, paragraph):
        """
        判断段落是否为列表项
        
        Args:
            paragraph: 段落对象
            
        Returns:
            是否为列表项
        """
        # 检查段落样式
        if paragraph.style.name.startswith('List'):
            return True
        
        # 检查段落文本特征
        list_patterns = [
            r'^\d+\.\s',  # 数字列表,如"1. "
            r'^[a-zA-Z]\.\s',  # 字母列表,如"a. "
            r'^[\u2022\u2023\u25E6\u2043\u2219]\s',  # 项目符号,如"• "
            r'^[-*]\s'  # 常见的项目符号,如"- "或"* "
        ]
        
        for pattern in list_patterns:
            if re.match(pattern, paragraph.text):
                return True
        
        return False
    
    def _get_list_level(self, paragraph):
        """
        获取列表项的级别
        
        Args:
            paragraph: 段落对象
            
        Returns:
            列表级别
        """
        # 根据缩进判断级别
        indent = paragraph.paragraph_format.left_indent
        if indent is None:
            return 1
        
        # 缩进值转换为级别(每级缩进约为0.5英寸)
        level = int(indent.pt / 36) + 1
        return max(1, min(level, 9))  # 限制在1-9之间

格式应用

接下来,我们实现格式应用功能,包括各种对齐方式的应用:

from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Pt

class FormatApplier:
    def __init__(self, document):
        """
        初始化格式应用器
        
        Args:
            document: Word文档对象
        """
        self.document = document
    
    def apply_alignment(self, paragraph_indices, alignment):
        """
        应用对齐方式
        
        Args:
            paragraph_indices: 段落索引列表
            alignment: 对齐方式
            
        Returns:
            成功应用的段落数量
        """
        alignment_map = {
            'left': WD_ALIGN_PARAGRAPH.LEFT,
            'center': WD_ALIGN_PARAGRAPH.CENTER,
            'right': WD_ALIGN_PARAGRAPH.RIGHT,
            'justify': WD_ALIGN_PARAGRAPH.JUSTIFY
        }
        
        if alignment not in alignment_map:
            raise ValueError(f"不支持的对齐方式: {alignment}")
        
        count = 0
        for idx in paragraph_indices:
            if 0 <= idx < len(self.document.paragraphs):
                self.document.paragraphs[idx].alignment = alignment_map[alignment]
                count += 1
        
        return count
    
    def apply_alignment_to_type(self, structure, element_type, alignment):
        """
        对指定类型的元素应用对齐方式
        
        Args:
            structure: 文档结构信息
            element_type: 元素类型('headings', 'normal_paragraphs', 'lists')
            alignment: 对齐方式
            
        Returns:
            成功应用的元素数量
        """
        if element_type not in structure:
            raise ValueError(f"不支持的元素类型: {element_type}")
        
        indices = [item['index'] for item in structure[element_type]]
        return self.apply_alignment(indices, alignment)
    
    def apply_table_alignment(self, table_index, row_indices=None, col_indices=None, alignment='left'):
        """
        对表格单元格应用对齐方式
        
        Args:
            table_index: 表格索引
            row_indices: 行索引列表,None表示所有行
            col_indices: 列索引列表,None表示所有列
            alignment: 对齐方式
            
        Returns:
            成功应用的单元格数量
        """
        alignment_map = {
            'left': WD_ALIGN_PARAGRAPH.LEFT,
            'center': WD_ALIGN_PARAGRAPH.CENTER,
            'right': WD_ALIGN_PARAGRAPH.RIGHT,
            'justify': WD_ALIGN_PARAGRAPH.JUSTIFY
        }
        
        if alignment not in alignment_map:
            raise ValueError(f"不支持的对齐方式: {alignment}")
        
        if table_index < 0 or table_index >= len(self.document.tables):
            raise ValueError(f"表格索引超出范围: {table_index}")
        
        table = self.document.tables[table_index]
        
        # 确定要处理的行和列
        if row_indices is None:
            row_indices = range(len(table.rows))
        if col_indices is None:
            col_indices = range(len(table.columns))
        
        count = 0
        for row_idx in row_indices:
            if 0 <= row_idx < len(table.rows):
                row = table.rows[row_idx]
                for col_idx in col_indices:
                    if 0 <= col_idx < len(row.cells):
                        cell = row.cells[col_idx]
                        for paragraph in cell.paragraphs:
                            paragraph.alignment = alignment_map[alignment]
                            count += 1
        
        return count
    
    def save(self, file_path=None):
        """
        保存文档
        
        Args:
            file_path: 保存路径,None表示覆盖原文件
            
        Returns:
            保存路径
        """
        save_path = file_path or self.document._path
        self.document.save(save_path)
        return save_path

使用COM接口实现高级功能

对于一些python-docx库无法直接支持的高级功能,我们使用pywin32库通过COM接口直接与Word应用程序交互:

import win32com.client
import os

class WordCOMHandler:
    def __init__(self):
        """初始化Word COM处理器"""
        self.word_app = None
        self.document = None
    
    def open_document(self, file_path):
        """
        打开Word文档
        
        Args:
            file_path: 文档路径
            
        Returns:
            是否成功打开
        """
        try:
            # 获取Word应用程序实例
            self.word_app = win32com.client.Dispatch("Word.Application")
            self.word_app.Visible = False  # 设置为不可见
            
            # 打开文档
            abs_path = os.path.abspath(file_path)
            self.document = self.word_app.Documents.Open(abs_path)
            return True
        
        except Exception as e:
            print(f"打开文档时出错: {e}")
            self.close()
            return False
    
    def apply_special_alignment(self, selection_type, alignment):
        """
        应用特殊对齐方式
        
        Args:
            selection_type: 选择类型('all', 'current_section', 'selection')
            alignment: 对齐方式
            
        Returns:
            是否成功应用
        """
        if not self.document:
            return False
        
        try:
            alignment_map = {
                'left': 0,  # wdAlignParagraphLeft
                'center': 1,  # wdAlignParagraphCenter
                'right': 2,  # wdAlignParagraphRight
                'justify': 3,  # wdAlignParagraphJustify
                'distribute': 4  # wdAlignParagraphDistribute
            }
            
            if alignment not in alignment_map:
                raise ValueError(f"不支持的对齐方式: {alignment}")
            
            # 根据选择类型设置选区
            if selection_type == 'all':
                self.word_app.Selection.WholeStory()
            elif selection_type == 'current_section':
                self.word_app.Selection.Sections(1).Range.Select()
            # 'selection'类型不需要额外操作,使用当前选区
            
            # 应用对齐方式
            self.word_app.Selection.ParagraphFormat.Alignment = alignment_map[alignment]
            return True
        
        except Exception as e:
            print(f"应用对齐方式时出错: {e}")
            return False
    
    def apply_alignment_to_tables(self, alignment):
        """
        对所有表格应用对齐方式
        
        Args:
            alignment: 对齐方式
            
        Returns:
            成功应用的表格数量
        """
        if not self.document:
            return 0
        
        try:
            alignment_map = {
                'left': 0,  # wdAlignParagraphLeft
                'center': 1,  # wdAlignParagraphCenter
                'right': 2,  # wdAlignParagraphRight
                'justify': 3,  # wdAlignParagraphJustify
                'distribute': 4  # wdAlignParagraphDistribute
            }
            
            if alignment not in alignment_map:
                raise ValueError(f"不支持的对齐方式: {alignment}")
            
            count = 0
            for i in range(1, self.document.Tables.Count + 1):
                table = self.document.Tables(i)
                table.Range.ParagraphFormat.Alignment = alignment_map[alignment]
                count += 1
            
            return count
        
        except Exception as e:
            print(f"应用表格对齐方式时出错: {e}")
            return 0
    
    def save_document(self, file_path=None):
        """
        保存文档
        
        Args:
            file_path: 保存路径,None表示覆盖原文件
            
        Returns:
            是否成功保存
        """
        if not self.document:
            return False
        
        try:
            if file_path:
                self.document.SaveAs(file_path)
            else:
                self.document.Save()
            return True
        
        except Exception as e:
            print(f"保存文档时出错: {e}")
            return False
    
    def close(self):
        """关闭文档和Word应用程序"""
        try:
            if self.document:
                self.document.Close(SaveChanges=False)
                self.document = None
            
            if self.word_app:
                self.word_app.Quit()
                self.word_app = None
        
        except Exception as e:
            print(f"关闭Word时出错: {e}")

历史记录管理

为了支持撤销功能,我们实现了一个简单的历史记录管理器:

import os
import shutil
import time

class HistoryManager:
    def __init__(self, max_history=10):
        """
        初始化历史记录管理器
        
        Args:
            max_history: 最大历史记录数量
        """
        self.max_history = max_history
        self.history = []
        self.current_index = -1
        
        # 创建临时目录
        self.temp_dir = os.path.join(os.environ['TEMP'], 'word_aligner_history')
        if not os.path.exists(self.temp_dir):
            os.makedirs(self.temp_dir)
    
    def add_snapshot(self, file_path):
        """
        添加文档快照
        
        Args:
            file_path: 文档路径
            
        Returns:
            快照ID
        """
        # 生成快照ID
        snapshot_id = f"snapshot_{int(time.time())}_{len(self.history)}"
        snapshot_path = os.path.join(self.temp_dir, f"{snapshot_id}.docx")
        
        # 复制文档
        shutil.copy2(file_path, snapshot_path)
        
        # 如果当前不是最新状态,删除后面的历史记录
        if self.current_index < len(self.history) - 1:
            for i in range(self.current_index + 1, len(self.history)):
                old_snapshot = self.history[i]
                old_path = os.path.join(self.temp_dir, f"{old_snapshot}.docx")
                if os.path.exists(old_path):
                    os.remove(old_path)
            
            self.history = self.history[:self.current_index + 1]
        
        # 添加新快照
        self.history.append(snapshot_id)
        self.current_index = len(self.history) - 1
        
        # 如果历史记录超过最大数量,删除最旧的记录
        if len(self.history) > self.max_history:
            oldest_snapshot = self.history[0]
            oldest_path = os.path.join(self.temp_dir, f"{oldest_snapshot}.docx")
            if os.path.exists(oldest_path):
                os.remove(oldest_path)
            
            self.history = self.history[1:]
            self.current_index -= 1
        
        return snapshot_id
    
    def can_undo(self):
        """
        检查是否可以撤销
        
        Returns:
            是否可以撤销
        """
        return self.current_index > 0
    
    def can_redo(self):
        """
        检查是否可以重做
        
        Returns:
            是否可以重做
        """
        return self.current_index < len(self.history) - 1
    
    def undo(self):
        """
        撤销操作
        
        Returns:
            撤销后的快照路径,如果无法撤销则返回None
        """
        if not self.can_undo():
            return None
        
        self.current_index -= 1
        snapshot_id = self.history[self.current_index]
        snapshot_path = os.path.join(self.temp_dir, f"{snapshot_id}.docx")
        
        if os.path.exists(snapshot_path):
            return snapshot_path
        else:
            return None
    
    def redo(self):
        """
        重做操作
        
        Returns:
            重做后的快照路径,如果无法重做则返回None
        """
        if not self.can_redo():
            return None
        
        self.current_index += 1
        snapshot_id = self.history[self.current_index]
        snapshot_path = os.path.join(self.temp_dir, f"{snapshot_id}.docx")
        
        if os.path.exists(snapshot_path):
            return snapshot_path
        else:
            return None
    
    def cleanup(self):
        """清理临时文件"""
        for snapshot_id in self.history:
            snapshot_path = os.path.join(self.temp_dir, f"{snapshot_id}.docx")
            if os.path.exists(snapshot_path):
                os.remove(snapshot_path)
        
        if os.path.exists(self.temp_dir) and not os.listdir(self.temp_dir):
            os.rmdir(self.temp_dir)

用户界面开发

为了提供良好的用户体验,我们使用PyQt5开发了一个直观的图形用户界面。

主窗口设计

import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                             QHBoxLayout, QPushButton, QLabel, QComboBox, 
                             QFileDialog, QMessageBox, QProgressBar, QAction, 
                             QToolBar, QStatusBar, QGroupBox, QRadioButton)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QIcon

class AlignmentWorker(QThread):
    """后台工作线程,用于处理耗时的对齐操作"""
    finished = pyqtSignal(bool, str)
    progress = pyqtSignal(int)
    
    def __init__(self, file_path, alignment_type, element_type):
        super().__init__()
        self.file_path = file_path
        self.alignment_type = alignment_type
        self.element_type = element_type
    
    def run(self):
        try:
            # 创建文档分析器
            analyzer = DocumentAnalyzer(self.file_path)
            structure = analyzer.analyze_structure()
            
            # 创建格式应用器
            applier = FormatApplier(analyzer.document)
            
            # 应用对齐方式
            count = 0
            total = len(structure[self.element_type]) if self.element_type in structure else 0
            
            for i, item in enumerate(structure.get(self.element_type, [])):
                indices = [item['index']]
                applier.apply_alignment(indices, self.alignment_type)
                count += 1
                self.progress.emit(int(i / total * 100) if total > 0 else 100)
            
            # 保存文档
            applier.save()
            
            self.finished.emit(True, f"成功对 {count} 个元素应用了{self.alignment_type}对齐")
        
        except Exception as e:
            self.finished.emit(False, f"操作失败: {str(e)}")

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()
        
        # 初始化历史记录管理器
        self.history_manager = HistoryManager()
        
        # 当前文档路径
        self.current_file = None
    
    def init_ui(self):
        """初始化用户界面"""
        self.setWindowTitle("Word快速文本对齐工具")
        self.setMinimumSize(600, 400)
        
        # 创建中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 主布局
        main_layout = QVBoxLayout(central_widget)
        
        # 文件选择区域
        file_group = QGroupBox("文档选择")
        file_layout = QHBoxLayout()
        
        self.file_label = QLabel("未选择文件")
        self.file_button = QPushButton("选择Word文档")
        self.file_button.clicked.connect(self.select_file)
        
        file_layout.addWidget(self.file_label, 1)
        file_layout.addWidget(self.file_button)
        file_group.setLayout(file_layout)
        main_layout.addWidget(file_group)
        
        # 对齐选项区域
        alignment_group = QGroupBox("对齐选项")
        alignment_layout = QVBoxLayout()
        
        # 对齐类型
        alignment_type_layout = QHBoxLayout()
        alignment_type_layout.addWidget(QLabel("对齐方式:"))
        
        self.alignment_combo = QComboBox()
        self.alignment_combo.addItems(["左对齐", "居中对齐", "右对齐", "两端对齐"])
        alignment_type_layout.addWidget(self.alignment_combo)
        alignment_layout.addLayout(alignment_type_layout)
        
        # 元素类型
        element_type_layout = QHBoxLayout()
        element_type_layout.addWidget(QLabel("应用于:"))
        
        self.element_combo = QComboBox()
        self.element_combo.addItems(["所有段落", "标题", "正文段落", "列表", "表格"])
        element_type_layout.addWidget(self.element_combo)
        alignment_layout.addLayout(element_type_layout)
        
        alignment_group.setLayout(alignment_layout)
        main_layout.addWidget(alignment_group)
        
        # 操作按钮区域
        button_layout = QHBoxLayout()
        
        self.apply_button = QPushButton("应用对齐")
        self.apply_button.setEnabled(False)
        self.apply_button.clicked.connect(self.apply_alignment)
        
        self.undo_button = QPushButton("撤销")
        self.undo_button.setEnabled(False)
        self.undo_button.clicked.connect(self.undo_action)
        
        self.redo_button = QPushButton("重做")
        self.redo_button.setEnabled(False)
        self.redo_button.clicked.connect(self.redo_action)
        
        button_layout.addWidget(self.apply_button)
        button_layout.addWidget(self.undo_button)
        button_layout.addWidget(self.redo_button)
        main_layout.addLayout(button_layout)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        main_layout.addWidget(

网站公告

今日签到

点亮在社区的每一天
去签到