山东大学软件学院2022级项目实训(七)

实现WPS插件中的错别字检测与标注

在我开发WPS插件时,我遇到了一个需要处理文档中错别字的问题。通过AI模型识别并返回错别字列表后,我的任务就是对这些错别字进行高亮、下划线标记,并提供正确的修改建议。以下是我在开发过程中使用的代码及其思考,如何通过实现这个功能。

功能概述

通过遍历文档中的内容,查找错别字并提供纠正建议。首先,它会接收一个包含错别字信息的对象(args2),然后根据上下文信息对错别字进行定位,最后在文档中标注出来,帮助用户轻松发现并修改错误。

具体的功能如下:

  1. 查找错别字:通过AI模型获取文本中的错别字,返回一个列表,其中包含每个错别字的错误词、建议改正的词以及所在的上下文。

  2. 按上下文分组:将错别字按照上下文片段分组,帮助我们更加准确地定位错别字的位置。

  3. 高亮显示:在文档中找到错别字后,使用红色字体并添加下划线标记,并在旁边添加修改建议。

问题与修改过程:

1.处理没有上下文的错别字

在实现功能时,最开始我并没有考虑到有些错别字是没有上下文的。这就导致了某些没有上下文的错别字无法被正确定位。比如,AI模型返回的错别字列表中,部分错误并没有附带具体的上下文信息。这样,当我尝试在文档中根据上下文查找错误时,对于没有上下文的错别字,我的查找操作会失败。

初始代码的问题

在最开始的代码实现中,我对错别字的处理是这样的:

typos.forEach(item => { 
const key = item.context; // 如果上下文为空,key 就是 null 或 undefined 
if (!groups.has(key)) groups.set(key, []); 
groups.get(key).push(item); 
});
调试过程

在调试时,我注意到文档中一些没有上下文的错别字没有被正确定位。在调试过程中,我输出了分组的内容,发现所有 contextnull 的错别字被归到了一个意外的组里,导致在后续的查找中无法正确处理这些错误。

解决方案

为了修复这个问题,我决定引入一个默认的分组标识——__NO_CTX__,用于统一处理没有上下文的错别字。这样即使某个错别字没有提供上下文,我也能将它归为一个统一的组,确保能够后续正确处理。

修改后的代码如下:

const groups = new Map(); 
typos.forEach(item => { 
const key = item.context || "__NO_CTX__"; // 如果没有上下文,使用默认分组 __NO_CTX__ 
if (!groups.has(key)) 
groups.set(key, []); groups.get(key).push(item); 
});

通过这种方式,所有没有上下文的错别字都会被归为一个统一的组 __NO_CTX__。这个分组让我能够在后续处理时清楚地知道哪些错误是没有上下文的,从而避免了错误遗漏。

2.避免重复标记同一错别字

在代码的另一个挑战中,我发现每当查找并标记一个错别字后,系统有时会重复标记同一个错误,特别是在全文查找过程中。这个问题主要发生在 globalSearchPos(全局搜索游标)没有正确更新的情况下。

初始代码的问题
let target = null; 
if (ctxRange) { 
// 在上下文范围内查找错别字 
const sub = ctxRange.Duplicate; 
const sf = sub.Find; 
sf.Text = wrong; 
if (sf.Execute()) target = sub; 
} 

if (!target) {
// 如果没有在上下文中找到,进行全文查找 
const r2 = doc.Range(globalSearchPos, docEnd); 
const f2 = r2.Find; 
f2.Text = wrong; 
if (f2.Execute()) target = r2.Duplicate; 
}

在这个过程中,我并没有使用游标 globalSearchPos 来记录上次查找的结束位置,因此每次进行全文查找时,文档会从头开始查找错别字,导致同一个错别字被重复标记。

调试过程

在调试过程中,我意识到,随着查找的进行,同一个错别字有时会被重复标记。通过打印日志,我发现每次全文查找时,globalSearchPos 并没有得到及时更新。因此,在每次查找时,系统都会从头开始查找,导致重复标记。

解决方案

为了解决这个问题,我在查找成功后,更新了 globalSearchPos,避免了重复标记。globalSearchPos 用来记录上次查找成功后的结束位置,确保每次查找都从上次的结束位置开始,避免从头开始查找。

修改后的代码如下:

if (f2.Execute()) { 
target = r2.Duplicate; 
globalSearchPos = r2.End + 1; // 更新游标,避免重复标记 
} 

最终代码

export function identifyTypos(args2) {
  const doc      = wps.Application.ActiveDocument;
  const fullText = doc.Content.Text;
  
  // 兼容 args2 为 JSON 字符串或对象
  const args = typeof args2 === "string" ? JSON.parse(args2) : args2;
  const typos = args.typos || [];

  // 按 context 分组
  const groups = new Map();
  typos.forEach(item => {
    const key = item.context || "__NO_CTX__";
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(item);
  });

  // 全文查找的游标(仅 fallback 时使用)
  let globalSearchPos = 1;
  const docEnd = doc.Content.End;

  // 遍历每个 context 分组
  groups.forEach((items, context) => {
    let ctxRange = null;

    // 如果有 context,就先定位这段范围
    if (context !== "__NO_CTX__") {
      const r = doc.Range(globalSearchPos, docEnd);
      const f = r.Find;
      f.Text    = context;
      f.Forward = true;
      f.Wrap    = wps.Enum.wdFindStop;
      if (f.Execute()) {
        ctxRange = r.Duplicate;  // 复制一份上下文范围
      }
    }

    // 对这一组里的每个 wrong
    items.forEach(item => {
      const { wrong, correct } = item;
      let target = null;

      if (ctxRange) {
        // 在上下文范围内再找一次 wrong
        const sub = ctxRange.Duplicate;
        const sf  = sub.Find;
        sf.Text    = wrong;
        sf.Forward = true;
        sf.Wrap    = wps.Enum.wdFindStop;
        if (sf.Execute()) target = sub;
      }

      // 如果 context 范围里没找到,再在全文里 fallback
      if (!target) {
        const r2 = doc.Range(globalSearchPos, docEnd);
        const f2 = r2.Find;
        f2.Text    = wrong;
        f2.Forward = true;
        f2.Wrap    = wps.Enum.wdFindStop;
        if (f2.Execute()) {
          target = r2.Duplicate;
          // 更新全局游标,避免重复标同一个 wrong
          globalSearchPos = r2.End + 1;
        }
      }

      if (target) {
        // 直接对找到的 Range 上色并下划线、添加注释
        target.Font.Color     = wps.Enum.wdColorRed;
        target.Font.Underline = wps.Enum.wdUnderlineSingle;
        doc.Comments.Add(
          target,
          `建议改为:${correct}`
        );
      } else {
        console.warn("既没在 context 也没在全文找到:", wrong);
      }
    });
  });

  console.log(`按 context 分组,高亮 ${typos.length} 处错别字`);
}


         {
            type: "function",
            function: {
              name: "identifyTypos",
              description: "找出文本中的错别字,并返回每个错别字、建议改正词,以及所在的上下文片段,仅在用户明确要求时调用",
              parameters: {
                type: "object",
                properties: {
                  typos: {
                    type: "array",
                    description: "检测到的所有错别字列表",
                    items: {
                      type: "object",
                      properties: {
                        wrong:   { type: "string", description: "错误词,请用双引号" },
                        correct: { type: "string", description: "建议改正词,请用双引号" },
                        context: { type: "string", description: "上下文片段,请用双引号" }
                      },
                      required: ["wrong","correct","context"]
                    }
                  }
                },
                required: ["typos"]
              }
            }
          }

1. 处理输入数据

  • 首先接收一个参数 args2,它可能是一个 JSON 字符串或者已经是对象。

  • 使用 typeof args2 === "string" ? JSON.parse(args2) : args2; 来确保 args2 被正确解析为对象。

2. 按上下文分组错别字

  • 通过 Map 将错别字按上下文分组,这样可以更精确地处理同一上下文中的错误。

  • 如果某个错别字没有上下文信息,我们就将其放入一个默认的分组 __NO_CTX__ 中。

3. 查找错别字位置

  • 使用 WPS 的 Find 方法查找每个错别字。首先在给定的上下文范围内查找,如果没有找到,再在全文中查找。

  • 为了避免重复标记同一个错误,我们使用 globalSearchPos 作为游标来记录上次查找结束的位置。

4. 标记错别字并提供建议

  • 找到错别字后,我们使用 target.Font.Color = wps.Enum.wdColorRed 来给错别字上色,target.Font.Underline = wps.Enum.wdUnderlineSingle 为其添加下划线。

  • 同时,使用 doc.Comments.Add 为该错别字添加注释,提供正确的建议。

总结:

通过这段代码,我学到了如何在 WPS 中与文档进行交互,如何使用 RangeFind API 来查找并修改文档内容。更重要的是,如何将 AI 提供的错误信息应用到实际的插件功能中,帮助用户更高效地进行文档校对。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值