You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现USFM与分号分隔文件互转及ListView展示?

词典软件输入自动化项目:USFM转换与MDF集成问题

项目背景

我正在开发个人项目,用于加速和自动化词典软件的输入流程,目前项目接近完成。核心需求如下:

  • 将词典软件导出的USFM记录转换为分号分隔格式,在ListView中展示数据
  • 编辑完成后,将数据转回原始USFM格式,导出回词典软件,再通过Multi-Dictionary Formatting (MDF)工具生成格式化词典

已尝试操作

  • 已成功将USFM文件(Sqlite DB导出的纯文本)加载到项目文本框,但未找到合适的USFM转换方法
  • 需要忽略文件前两行头部信息,仅在ListView中展示词汇记录;转回USFM时必须保留这两行头部
  • 每条词汇记录以\lx(词位)开头,以\dt(日期戳)结尾

USFM示例

\_sh v3.0  400  MDF
\_DateStampHasFourDigitYear

\lx ahapei pyt
\ps sub
\ge Earring
\gn Brinco

\xv
\xe
\xn

\sd Artesanato

\dt 18/Apr/2023

\lx ahasep
\ps vb
\ge Extinguish (fire, lamp); Switch off (light)
\gn Apagar (fogueira, lamparina); Desligar (luz)

\xv
\xe
\xn

\dt 18/Apr/2023

\lx ahehay
\ps vb.
\ge Converse; Talk
\gn Falar; Conversar

\xv ahehay yn aiwyria'in wywo.
\xe we alway talk with our family/relatives.
\xn nós sempre falamos com nossos familiares/parentes.

\dt 03/Feb/2023

\lx ahek
\ps adj.
\ge Breathless
\gn Fôlego (sem)

\xv ase'i tuwepot'am yity'ok apypekiat kape hawyi sese i'ahek.
\xe after the older man/grandfather climbed up to the top of the hill
he was really breathless.
\xn depois que o homem mais velho/vovô subiu até o topo do morro
ele estava muito sem fôlego.

\cf imo'ahek

\dt 10/May/2023

需求细节

  • 不同词汇记录的USFM标记数量可能不同,部分字段(如\xv、\xe、\xn)可能为空,空字段需用分号占位以匹配ListView列
  • 词位(\lx内容)可能是单个或多个单词
  • 可选择两种实现路径:要么先转成分号分隔格式再展示到ListView,要么直接在ListView中解析展示USFM记录(无需文本框中转)
  • 需要了解如何在Visual Studio的Windows Forms项目中集成MDF工具,实现完整的格式化词典输出

已实现的文件打开代码(VB.NET)

Dim myStream As Stream = Nothing
Dim ofd As New OpenFileDialog()

With ofd
    .InitialDirectory = "C:\Users\jmwil\Documents\Lexique Pro\Data"
    .Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"
    .FilterIndex = 2
    .RestoreDirectory = True

    If ofd.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
        Try
            myStream = ofd.OpenFile()
            If (myStream IsNot Nothing) Then
                ' 打开选中的文件名到文本框
                TextBox35.Text = ofd.FileName

                Dim fileReader As String
                fileReader = My.Computer.FileSystem.ReadAllText(ofd.FileName.ToString,
                   System.Text.Encoding.UTF32)
                'MsgBox(fileReader)
                TextBox36.Text = fileReader
            End If
        Catch Ex As Exception
            MessageBox.Show("Cannot read file from disk. Original error: " & Ex.Message)
        Finally
            ' 确保流已关闭
            If (myStream IsNot Nothing) Then
                myStream.Close()
            End If
        End Try
    End If
End With

解决方案步骤

一、USFM解析与分号格式转换

  1. 分离头部与记录
    读取文件后拆分内容为行,提取前两行作为头部保存,剩余行用于处理词汇记录:

    Dim allLines As String() = fileReader.Split({vbCrLf, vbLf}, StringSplitOptions.RemoveEmptyEntries)
    Dim headerLines As New List(Of String)()
    Dim recordLines As New List(Of String)()
    Dim isHeaderDone As Boolean = False
    
    For Each line As String In allLines
        If Not isHeaderDone Then
            headerLines.Add(line.Trim())
            If headerLines.Count = 2 Then
                isHeaderDone = True
            End If
        Else
            recordLines.Add(line.Trim())
        End If
    Next
    
  2. 解析单个词汇记录
    遍历行内容,按\lx分割出每个词汇块,提取各标记对应的内容:

    Dim records As New List(Of Dictionary(Of String, String))()
    Dim currentRecord As Dictionary(Of String, String) = Nothing
    
    For Each line As String In recordLines
        If line.StartsWith("\lx") Then
            ' 新记录开始,保存上一条(如果存在)
            If currentRecord IsNot Nothing Then
                records.Add(currentRecord)
            End If
            currentRecord = New Dictionary(Of String, String)()
            currentRecord("lx") = line.Substring(3).Trim()
        ElseIf line.StartsWith("\dt") Then
            ' 记录结束,添加日期并保存
            currentRecord("dt") = line.Substring(3).Trim()
            records.Add(currentRecord)
            currentRecord = Nothing
        ElseIf line.StartsWith("\") Then
            ' 处理其他标记
            Dim tag As String = line.Substring(1).Split(" "c)(0)
            Dim content As String = line.Substring(tag.Length + 2).Trim()
            currentRecord(tag) = content
        End If
    Next
    
  3. 转换为分号分隔格式并绑定到ListView
    定义展示列,遍历记录生成每行内容,空字段用分号占位,最后绑定到ListView:

    Dim csvLines As New List(Of String)()
    Dim columns As String() = {"lx", "ps", "ge", "gn", "xv", "xe", "xn", "sd", "cf", "dt"}
    ' 添加表头
    csvLines.Add(String.Join(";", columns))
    
    For Each rec In records
        Dim lineParts As New List(Of String)()
        For Each col In columns
            If rec.ContainsKey(col) Then
                ' 转义内容中的分号
                lineParts.Add(rec(col).Replace(";", "\;"))
            Else
                lineParts.Add("")
            End If
        Next
        csvLines.Add(String.Join(";", lineParts))
    Next
    
    ' 设置ListView列并绑定数据
    ListView1.Columns.AddRange(columns.Select(Function(c) New ColumnHeader() With {.Text = c}).ToArray())
    For Each line In csvLines.Skip(1) ' 跳过表头
        Dim parts As String() = line.Split(";"c)
        Dim item As New ListViewItem(parts(0))
        For i = 1 To parts.Length - 1
            item.SubItems.Add(parts(i).Replace("\;", ";"))
        Next
        ListView1.Items.Add(item)
    Next
    
  4. 从分号格式转回USFM
    读取ListView内容,按USFM格式重新拼接,加上保存的头部后导出:

    Dim sb As New StringBuilder()
    ' 添加头部
    sb.AppendLine(headerLines(0))
    sb.AppendLine(headerLines(1))
    sb.AppendLine()
    
    For Each item As ListViewItem In ListView1.Items
        ' 提取每个字段
        Dim rec As New Dictionary(Of String, String)()
        For i = 0 To columns.Length - 1
            rec(columns(i)) = item.SubItems(i).Text.Replace("\;", ";")
        Next
    
        ' 按USFM格式拼接
        sb.AppendLine($"\lx {rec("lx")}")
        If Not String.IsNullOrEmpty(rec("ps")) Then sb.AppendLine($"\ps {rec("ps")}")
        If Not String.IsNullOrEmpty(rec("ge")) Then sb.AppendLine($"\ge {rec("ge")}")
        If Not String.IsNullOrEmpty(rec("gn")) Then sb.AppendLine($"\gn {rec("gn")}")
        ' 处理空的xv/xe/xn
        sb.AppendLine($"\xv {rec("xv")}")
        sb.AppendLine($"\xe {rec("xe")}")
        sb.AppendLine($"\xn {rec("xn")}")
        sb.AppendLine()
        If Not String.IsNullOrEmpty(rec("sd")) Then sb.AppendLine($"\sd {rec("sd")}")
        If Not String.IsNullOrEmpty(rec("cf")) Then sb.AppendLine($"\cf {rec("cf")}")
        sb.AppendLine($"\dt {rec("dt")}")
        sb.AppendLine()
    Next
    
    ' 保存为USFM文件
    My.Computer.FileSystem.WriteAllText("output.usfm", sb.ToString(), False, System.Text.Encoding.UTF32)
    

二、Windows Forms集成MDF工具

MDF工具通常为命令行程序,可通过Process类调用:

  1. 将MDF工具添加到项目
    将MDF可执行文件(如mdf.exe)复制到项目输出目录,或在代码中指定完整路径。
  2. 调用命令行执行MDF
    Dim mdfPath As String = "C:\Path\To\mdf.exe"
    Dim inputUsfmPath As String = "output.usfm"
    Dim outputPath As String = "formatted_dictionary.html" ' 或MDF支持的其他格式
    
    Dim processStartInfo As New ProcessStartInfo(mdfPath)
    processStartInfo.Arguments = $" -i {inputUsfmPath} -o {outputPath}"
    processStartInfo.WindowStyle = ProcessWindowStyle.Hidden
    processStartInfo.CreateNoWindow = True
    
    Using process As Process = Process.Start(processStartInfo)
        process.WaitForExit()
        ' 检查退出码判断是否成功
        If process.ExitCode = 0 Then
            MessageBox.Show("词典格式化完成")
        Else
            MessageBox.Show("MDF执行失败")
        End If
    End Using
    

内容的提问来源于stack exchange,提问作者John Michael Wilkinson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 03:32:13