fdu高级软件开发技术小组作业
本实验要求实现一个基于命令行的文本编辑器,支持同时打开多个文本文件,提供工作区管理、日志记录、状态持久化等功能。
实验重点考察:
- 面向对象建模能力
- 模块化设计与依赖管理
- 设计模式的合理应用
- 自动化测试能力
核心职责:
- 管理当前会话的全局状态,包括已打开文件列表、当前活动文件、文件修改状态、日志开关等
- 协调命令与具体编辑器之间的交互,例如
load/save/close等操作 - 状态持久化:程序退出后能保存工作区状态,下次启动时恢复
- 发布事件供日志记录等模块订阅
设计要点:
- 工作区中可以有多个编辑中的文件(Editor),有一个当前的活动文件(Active Editor)
- 每个Editor有独立的undo/redo状态
- 持久化保存的内容:
- 打开的文件列表
- 当前活动文件
- 文件修改状态(modified标记)
- 日志开关状态
- 不需要持久化的内容:undo/redo历史记录
建议使用的设计模式:
- 备忘录模式 (Memento):用于工作区状态的持久化和恢复
- 观察者模式 (Observer):用于事件通知机制
Lab1仅支持文本编辑器即可,在Lab2中将增加XML编辑器。
功能职责:
- 支持基本文本编辑操作:追加(append)、插入(insert)、删除(delete)、替换(replace)
- 支持显示操作:指定行号范围显示文本内容(show)
- 所有编辑操作后自动标记文件为已修改
- 提供必要的错误反馈(如范围越界)
数据结构要求:
- 使用行数组(
List<String>)存储文本,每个元素是一行 - 保存时用换行符连接各行
- 这样可以方便地通过行号定位和操作
建议使用的设计模式:
- 命令模式 (Command):实现undo/redo功能
- 装饰器模式 (Decorator)
文本文件示例:
The quick brown fox
jumps over the lazy dog.
This line contains extra spaces.核心功能:记录每一次命令执行,包括执行的时间戳,并持久化到日志文件中。
功能职责:
- 若文件第一行是
# log,则打开该文件时自动启用日志记录 - 记录每一条命令的执行内容与时间戳
- 每次程序启动视为一次新的会话(Session),日志以时间段划分
- 支持日志开关,可通过命令手动启用/关闭(
log-on/log-off) - 支持查看日志记录(
log-show) - 日志写入
.filename.log文件,永久保存 - 若日志记录失败仅提示警告,不中断程序正常运行
建议使用的设计模式:
- 观察者模式 (Observer):日志模块作为观察者监听命令执行事件
日志文件格式:
session start at 20251024 09:41:33
20251024 09:41:40 load lab.txt
20251024 09:42:05 append "test line"
20251024 09:44:27 save
20251024 09:44:50 close格式说明:
- 每条命令一行,格式为:
时间戳 命令参数 - 会话开始用
session start at标识 - 命令参数与用户交互时保持一致
示例场景:
文件 lab.txt 内容:
# log
今天是个写代码的好日子
记得把实验报告补完打开该文件后,自动在 .lab.txt.log 中记录操作。
- 命令默认对当前活动文件生效
<file>表示文件路径line:col表示行号和列号(从1开始计数)- 带空格的文本参数使用双引号包裹
- 所有命令参数区分大小写
- 文件编码格式:统一使用UTF-8编码
| 命令 | 功能 | 必需参数 | 可选参数 |
|---|---|---|---|
load <file> |
加载文件 | 文件路径 | - |
save [file|all] |
保存文件 | - | file/all |
init <file> [with-log] |
创建新缓冲区 | 文件 | with-log |
close [file] |
关闭文件 | - | file |
edit <file> |
切换活动文件 | 文件 | - |
editor-list |
显示文件列表 | - | - |
dir-tree [path] |
显示目录树 | - | path |
undo |
撤销 | - | - |
redo |
重做 | - | - |
exit |
退出程序 | - | - |
| 命令 | 功能 | 适用文件 |
|---|---|---|
append "text" |
追加文本 | .txt |
insert <line:col> "text" |
插入文本 | .txt |
delete <line:col> <len> |
删除字符 | .txt |
replace <line:col> <len> "text" |
替换文本 | .txt |
show [start:end] |
显示内容 | .txt |
| 命令 | 功能 |
|---|---|
log-on [file] |
启用日志 |
log-off [file] |
关闭日志 |
log-show [file] |
显示日志 |
load <file>功能:加载文件。
行为:
- 文件已存在:读取并解析内容
- 文件不存在:创建新文件,标记为已修改
- 文件成为当前活动文件
save [file|all]功能:保存文件内容到磁盘。
参数说明:
- 不指定参数:保存当前活动文件
file:保存指定文件all:保存所有已打开的文件
行为:
- 保存成功后清除已修改标记
- 若路径无法写入,提示错误信息
init <file> [with-log]功能:创建一个未保存的新缓冲文件,并初始化基础结构。
参数说明:
file:创建纯文本文件with-log(可选):是否在第一行添加# log以启用日志
初始化内容:
创建文本文件(init test.text with-log):
# log说明:
- 新缓冲区标记为已修改,需要使用
save命令指定路径保存 - 创建后自动成为当前活动文件
close [file]功能:关闭当前活动文件或指定文件。
行为:
- 文件已修改且未保存:提示"文件已修改,是否保存? (y/n)"
- 关闭后,如果还有其他打开的文件,切换到最近使用的文件
edit <file>功能:切换当前活动文件。
行为:
- 文件必须已在工作区中打开
- 切换失败提示:"文件未打开: [file]"
editor-list功能:显示工作区中所有打开的文件及其状态。
显示格式(可选以下任一种):
格式1:
* file1.txt [modified]
file2.txt
格式2:
> file1.txt*
file2.txt
说明:
- 当前活动文件标记:
*(格式1)或>(格式2) - 已修改未保存标记:
[modified](格式1)或后缀*(格式2)
dir-tree [path]功能:以树形结构显示当前工作目录(或指定目录)的文件和文件夹。
参数说明:
- 不指定参数:显示当前工作目录
path:指定要显示的目录路径
显示格式:
├── visitor
│ ├── visitor.worksheet.sc
│ ├── visitor.scala
│ ├── README.md
│ ├── diagram.md
│ └── livedemo
│ ├── visitor.scala
│ └── livedemo.worksheet.sc
└── strategy
├── README.md
├── livedemo
│ ├── strategy.scala
│ └── strategy.worksheet.sc
├── strategy.scala
└── strategy.worksheet.sc
说明:
- 使用
├──、└──和│字符绘制树形结构 - 显示目录和文件的层级关系
undo功能:撤销上一次编辑操作。
说明:
- 只撤销会改变文件状态的命令
- 显示类命令(show、dir-tree、editor-list等)不进入撤销栈
redo功能:重做上一次撤销的操作。
exit功能:退出编辑器程序。
行为:
- 保存工作区状态到配置文件
- 若有未保存的文件,逐一提示是否保存
append "text"功能:在文件末尾追加一行文本。
示例:
原文:
Hello world
执行: append "New line"
结果:
Hello world
New line
insert <line:col> "text"功能:在指定位置插入文本。
参数说明:
line:行号(从1开始)col:列号(从1开始,表示插入到第几个字符前)"text":要插入的文本内容
行为说明:
- 插入位置在现有字符之前
- 文本中可以包含换行符(
\n),将自动拆分为多行
异常处理:
- 行号或列号越界:提示"行号或列号越界"
- 空文件插入非1:1位置:提示"空文件只能在1:1位置插入"
示例:
原文:
abcdef
执行: insert 1:4 "XYZ"
结果:
abcXYZdef
delete <line:col> <len>功能:从 line:col 位置开始,删除连续 len 个字符。
行为说明:
- 删除范围不可跨行
- 删除长度不可超过该行剩余字符数
异常处理:
- 删除长度超出该行剩余字符:提示"删除长度超出行尾"
- 行号或列号越界:提示相应的范围错误
示例:
原文:
Hello world
执行: delete 1:7 5
结果:
Hello
replace <line:col> <len> "text"功能:删除从指定位置起 len 个字符,并插入 "text"。
行为说明:
- 等效于先执行
delete,再执行insert - 替换文本可为空字符串,效果等同于删除
示例:
原文:
fast fox
执行: replace 1:1 4 "slow"
结果:
slow fox
show [startLine:endLine]功能:显示文本编辑器中指定范围的内容(按行)。
参数说明:
- 不指定参数:显示全文
startLine:起始行号(从1开始)endLine:结束行号(包含)
适用对象:仅适用于文本编辑器(.txt 文件)
示例:
> show
1: Hello world
2: This is line 2
3: This is line 3
> show 1:2
1: Hello world
2: This is line 2说明:显示类命令不改变文件状态,不进入撤销栈
log-on [file]功能:为指定文件(或当前活动文件)启用日志记录。
参数说明:
- 不指定参数:为当前活动文件启用日志
file:为指定文件启用日志
行为说明:
- 后续该文件的所有编辑操作、保存行为将被记录到
.filename.log文件中 - 如果文件首行已是
# log,可自动启用
log-off [file]功能:关闭对指定文件(或当前活动文件)的日志记录。
参数说明:
- 不指定参数:关闭当前活动文件的日志
file:关闭指定文件的日志
行为说明:
- 停止监听该文件的日志事件,但不删除已有日志
log-show [file]功能:显示指定文件(或当前活动文件)的日志记录。
参数说明:
- 不指定参数:显示当前活动文件的日志
file:显示指定文件的日志
输出格式:显示 .filename.log 文件的内容。
本实验在Lab1的基础上要求实现一个基于命令行的多文件编辑器,包含纯文本编辑器与XML编辑器两类,增加编辑时长统计、拼写检查两大模块。
XML说明: XML (eXtensible Markup Language) 是一种用于存储和传输数据的标记语言,具有良好的可读性和可扩展性,广泛应用于配置文件、数据交换等场景。
功能职责:
- 支持元素级编辑操作:插入元素(insert-before)、追加子元素(append-child)、修改元素ID(edit-id)、修改元素文本(edit-text)、删除元素(delete)
- 支持树形结构的可视化输出(xml-tree)
- 支持拼写检查功能:扫描文档文本节点并输出拼写错误报告
数据结构要求:
- 解析XML文件为树形结构(DOM树)
- 内部维护元素节点,并建立
id -> element的映射以支持快速查找 - 保存时序列化回XML格式
建议使用的设计模式:
- 命令模式 (Command):实现undo/redo功能
- 组合模式 (Composite):表示XML树形结构
- 装饰器模式 (Decorator):自动标记文件修改状态
XML文件示例:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore id="root">
<book id="book1" category="COOKING">
<title id="title1" lang="en">Everyday Italian</title>
<author id="author1">Giada De Laurentiis</author>
<year id="year1">2005</year>
<price id="price1">30.00</price>
</book>
<book id="book2" category="CHILDREN">
<title id="title2" lang="en">Harry Potter</title>
<author id="author2">J K. Rowling</author>
</book>
</bookstore>XML语法规则:
<?xml version="1.0" encoding="UTF-8" ?>必须写在首行(除非第一行是# log注释)- 根标签只能有一个,子标签可以有多个,且必须成对存在
- 标签属性值必须用双引号包起来
- 每个元素必须有唯一的
id属性,用于命令操作中的元素定位 - 本次实验不考虑注释、自闭合标签等高级特性
重要说明:
-
要求所有元素都有id是为了简化实现。在实际应用中XML元素不一定都有id,但在本实验中统一要求所有元素必须有唯一id
-
元素定位:所有XML编辑命令中的元素ID参数都是指元素的
id属性值,可以通过id精确定位任何元素
核心功能:记录每个文件在当前会话(Session)中的编辑时长,并以可读格式显示。
会话(Session)定义:
- 会话:从程序启动到退出的一次完整运行周期
- 每次启动程序开始新的会话,所有文件的编辑时长重置为0
- 工作区状态恢复不会恢复编辑时长
时长计算规则:
- 开始计时:当文件成为活动文件时(通过
load或edit命令) - 停止计时:当切换到其他文件、关闭文件或退出程序时
- 累计时长:一个会话中,文件每次成为活动文件都会累计时长
- 重置时长:文件关闭后,如果再次打开(
load),时长重置为0
显示要求:
- 在
editor-list命令中,每个文件名后显示编辑时长 - 使用可读格式:根据时长大小自动选择合适单位
时长格式规范:
| 时长范围 | 显示格式 | 示例 |
|---|---|---|
| < 1分钟 | X秒 | 45秒 |
| 1-59分钟 | X分钟 | 25分钟 |
| 1-23小时 | X小时Y分钟 | 2小时15分钟 |
| ≥ 24小时 | X天Y小时 | 1天3小时 |
建议使用的设计模式:
- 装饰器模式 (Decorator):在显示文件列表时,为每个文件名添加时长信息
- 观察者模式 (Observer):监听文件切换事件,自动更新时长统计
说明:
- 统计模块是相对独立的横切功能,不应与核心编辑功能强耦合
- 如果统计功能失败,应仅提示警告,不影响其他功能继续执行
核心考察点:主要考察架构设计能力和第三方库管理能力,而非算法实现。
选择合适的拼写检查服务,实现对编辑器中的文本内容进行拼写检查,并报告错误。
可以考虑以下拼写检查服务:
API:https://dev.languagetool.org/public-http-api
Java:https://dev.languagetool.org/java-api
Python:spell-checker库或pyspellchecker库
考察重点:
- 依赖隔离:第三方库依赖被限制在适配器内
- 接口抽象:定义清晰接口,编辑器依赖接口而非实现
- 依赖注入:依赖从外部传入,而非内部创建
- 可测试性:使用Mock对象测试,无需真实库
建议使用的设计模式: 适配器模式 (Adapter)
日志模块在不改变核心行为的基础上,新增“按文件首行配置的日志过滤”能力。
- 核心功能:通过文件首行的
# log行为该文件启用日志,并可追加参数过滤不需要记录的命令。 - 语法规则:
# log:启用该文件的日志记录(保持原有行为)。# log -e <cmd> [-e <cmd> ...]:排除指定命令的日志记录,-e可重复出现以排除多个命令。
- 示例:
- 首行写入
# log -e append -e delete表示不记录该文件的append与delete命令日志。
- 首行写入
- 行为说明:
- 过滤仅作用于该文件的日志记录,适用于文本编辑命令与XML编辑命令(例如
insert-before、append-child等)。 - 未识别或不存在的命令名将被忽略,并在日志模块内以告警方式提示;不影响程序正常运行。
- 日志写入失败仅提示警告,不阻断编辑流程(与既有日志策略一致)。
- 过滤仅作用于该文件的日志记录,适用于文本编辑命令与XML编辑命令(例如
在Lab2中,相比Lab1的命令集有以下新增和变动:
| 命令 | 功能 | 必需参数 | 可选参数 |
|---|---|---|---|
init <text|xml> [with-log] |
创建新缓冲区,增加xml文件 | 文件类型 | with-log |
editor-list |
显示文件列表(支持时长显示) | - | - |
| 命令 | 功能 | 适用文件 |
|---|---|---|
insert-before <tag> <newId> <targetId> ["text"] |
插入元素 | .xml |
append-child <tag> <newId> <parentId> ["text"] |
追加子元素 | .xml |
edit-id <oldId> <newId> |
修改元素ID | .xml |
edit-text <elementId> ["text"] |
修改元素文本 | .xml |
delete <elementId> |
删除元素 | .xml |
xml-tree [file] |
显示XML树 | .xml |
| 命令 | 功能 | 适用文件 |
|---|---|---|
spell-check [file] |
拼写检查 | .txt .xml |
init <text|xml> [with-log]功能:创建一个未保存的新缓冲文件,并初始化基础结构。
参数说明:
text:创建纯文本文件xml:创建XML文件,写入合法的空结构with-log(可选):是否在第一行添加# log以启用日志
初始化内容:
创建文本文件(init text with-log):
# log创建XML文件(init xml):
<?xml version="1.0" encoding="UTF-8"?>
<root id="root">
</root>说明:
- 新缓冲区标记为已修改,需要使用
save命令指定路径保存 - 创建后自动成为当前活动文件
editor-list功能:显示工作区中所有打开的文件及其状态。
显示格式(可选以下任一种):
格式1:
* file1.txt [modified] (2小时15分钟)
file2.xml (45秒)
格式2:
> file1.txt* (2小时15分钟)
file2.xml (45秒)
说明:
-
当前活动文件标记:
*(格式1)或>(格式2) -
已修改未保存标记:
[modified](格式1)或后缀*(格式2) -
编辑时长:括号内显示当前会话中的编辑时长
insert-before <tagName> <newId> <targetId> ["text"]功能:在目标元素前(同级)插入一个新元素。
参数说明:
tagName:新插入元素的标签名newId:新元素的唯一ID,不可与已有元素重复targetId:目标元素的ID,新元素将被插入到该元素前"text":可选,新元素的文本内容
异常处理:
newId已存在:提示"元素ID已存在: [newId]"targetId不存在:提示"目标元素不存在: [targetId]"- 尝试在根元素前插入:提示"不能在根元素前插入元素"
示例:
insert-before book newBook book1 ""append-child <tagName> <newId> <parentId> ["text"]功能:在某元素内追加一个子元素(作为最后一个子元素)。
参数说明:
tagName:要追加的子元素标签名newId:子元素ID,需唯一parentId:父元素ID"text":可选,子元素的文本内容
异常处理:
parentId无效:提示"父元素不存在: [parentId]"newId重复:提示"元素ID已存在: [newId]"
示例:
append-child price price4 book1 "29.99"edit-id <oldId> <newId>功能:修改某个元素的ID。
参数说明:
oldId:原始ID,必须存在newId:目标ID,必须未被占用
异常处理:
oldId不存在:提示"元素不存在: [oldId]"newId已被占用:提示"目标ID已存在: [newId]"- 尝试修改根元素ID:提示"不建议修改根元素ID"
示例:
edit-id book1 book001edit-text <elementId> ["text"]功能:修改某元素的文本内容。
参数说明:
elementId:元素的ID"text":新文本内容(可选),若为空字符串或省略则清空原内容
异常处理:
elementId不存在:提示"元素不存在: [elementId]"
示例:
edit-text title1 "New Book Title"delete <elementId>功能:删除指定ID的元素(包括其所有子元素)。
异常处理:
elementId不存在:提示"元素不存在: [elementId]"- 尝试删除根元素:提示"不能删除根元素"
示例:
delete book1xml-tree [file]功能:以树形结构打印XML文件内容,展示元素的层级关系、属性和文本内容。
参数说明:
- 不指定参数:显示当前活动文件
file:显示指定XML文件
适用对象:仅适用于XML编辑器(.xml 文件)
输出格式要求:
- 使用树形字符(
├──、└──、│)或缩进表示层级关系 - 显示元素的所有属性(包括id)
- 显示元素的文本内容(如果有)
示例:
原XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<bookstore id="root">
<book id="book1" category="COOKING">
<title id="title1" lang="en">Everyday Italian</title>
</book>
</bookstore>输出:
bookstore [id="root"]
├── book [id="book1", category="COOKING"]
│ └── title [id="title1", lang="en"]
│ └── "Everyday Italian"
说明:两种格式均可,推荐使用树形字符以获得更好的可视化效果。显示类命令不改变文件状态,不进入撤销栈
spell-check [file]功能:检查文本文件、xml文件中的拼写错误。
参数说明:
- 不指定参数:检查当前活动文件
file:检查指定文本文件
输出格式参考:
拼写检查结果:
第1行,第5列: "recieve" -> 建议: receive
第3行,第12列: "occured" -> 建议: occurred
拼写检查结果:
元素 title1: "Itallian" -> 建议: Italian
元素 author2: "Rowlling" -> 建议: Rowling