Excel中的富文本格式(带格式标签)转换为未格式化文本

3

我在Excel中有大约12000个单元格包含RTF(包括格式标签)。 我需要解析它们以获取未经格式化的文本。

以下是其中一个带有文本的单元格示例:

{\rtf1\ansi\deflang1060\ftnbj\uc1
{\fonttbl{\f0 \froman \fcharset0 Times New Roman;}{\f1 \fswiss \fcharset238
Arial;}}
{\colortbl ;\red255\green255\blue255 ;\red0\green0\blue0 ;}
{\stylesheet{\fs24\cf2\cb1 Normal;}{\cs1\cf2\cb1 Default Paragraph Font;}}
\paperw11908\paperh16833\margl1800\margr1800\margt1440\margb1440\headery720\footery720
\deftab720\formshade\aendnotes\aftnnrlc\pgbrdrhead\pgbrdrfoot
\sectd\pgwsxn11908\pghsxn16833\marglsxn1800\margrsxn1800\margtsxn1440\margbsxn1440
\headery720\footery720\sbkpage\pgncont\pgndec
\plain\plain\f1\fs24\pard TPR 0160 000\par IPR 0160 000\par OB-R-02-28\par}

我需要的只有这个:

TPR 0160 000
IPR 0160 000
OB-R-02-28

简单循环遍历单元格并删除不必要的格式的问题在于,那12000个单元格中并非所有内容都像这样直截了当。因此,我需要手动检查许多不同版本并书写几种变体;即使到最后还有很多手工操作要完成。
但是,如果我将一个单元格的内容复制到空白文本文档中,并将其保存为RTF,然后用MS Word打开它,它会立即解析文本,并给我想要的结果。不幸的是,对于12000个单元格来说这样做实在太不方便了。
因此,我考虑使用VBA宏,将单元格内容移动到Word中,强制解析,然后将结果复制回原始单元格。不幸的是,我不太确定该如何实现。
有人有什么想法吗?或者有其他的方法吗?如果能提供解决方案或指导,我会非常感激。
谢谢!

使用Microsoft Rich Textbox控件的更简便的替代方案。https://dev59.com/qZ_ha4cB1Zd3GeqP2qtL#42579833 - Slai
4个回答

8
如果你想使用Word来解析文本,这个函数应该能帮到你。正如注释所示,你需要引用MS Word对象库。
Function ParseRTF(strRTF As String) As String
Dim wdDoc As Word.Document 'Ref: Microsoft Word 11.0 Object Library'
Dim f     As Integer       'Variable to store the file I/O number'

'File path for a temporary .rtf file'
Const strFileTemp = "C:\TempFile_ParseRTF.rtf"

'Obtain the next valid file I/O number'
f = FreeFile

'Open the temp file and save the RTF string in it'
Open strFileTemp For Output As #f
    Print #f, strRTF
Close #f

'Open the .rtf file as a Word.Document'
Set wdDoc = GetObject(strFileTemp)

'Read the now parsed text from the Word.Document'
ParseRTF = wdDoc.Range.Text

'Delete the temporary .rtf file'
Kill strFileTemp

'Close the Word connection'
wdDoc.Close False
Set wdDoc = Nothing
End Function

您可以使用类似以下代码的方式,为您的12,000个单元格中的每一个调用它:
Sub ParseAllRange()
Dim rngCell As Range
Dim strRTF  As String

For Each rngCell In Range("A1:A12000")

    'Parse the cell contents'
    strRTF = ParseRTF(CStr(rngCell))

    'Output to the cell one column over'
    rngCell.Offset(0, 1) = strRTF
Next
End Sub

ParseRTF函数需要大约一秒钟才能运行(至少在我的机器上),因此对于12,000个单元格,这将需要大约三个半小时。经过周末的思考,我确信有一个更好(更快)的解决方案。我想起了剪贴板的RTF功能,并意识到可以创建一个类来将RTF数据复制到剪贴板,粘贴到Word文档中,并输出结果纯文本。这种解决方案的好处是,Word文档对象不必为每个rtf字符串打开和关闭;它可以在循环之前打开并在之后关闭。以下是实现此目的的代码。它是一个名为clsRTFParser的类模块。
Private Declare Function GlobalAlloc Lib "kernel32" _
                (ByVal wFlags&, ByVal dwBytes As Long) As Long
Private Declare Function GlobalLock Lib "kernel32" _
                (ByVal hMem As Long) As Long
Private Declare Function GlobalUnlock Lib "kernel32" _
                (ByVal hMem As Long) As Long
Private Declare Function lstrcpy Lib "kernel32" _
                (ByVal lpString1 As Any, ByVal lpString2 As Any) As Long

Private Declare Function OpenClipboard Lib "user32" _
                (ByVal Hwnd As Long) As Long
Private Declare Function EmptyClipboard Lib "user32" () As Long
Private Declare Function RegisterClipboardFormat Lib "user32" Alias _
                "RegisterClipboardFormatA" (ByVal lpString As String) As Long
Private Declare Function SetClipboardData Lib "user32" _
                (ByVal wFormat As Long, ByVal hMem As Long) As Long
Private Declare Function CloseClipboard Lib "user32" () As Long

'---'

Dim wdDoc As Word.Document 'Ref: Microsoft Word 11.0 Object Library'

Private Sub Class_Initialize()
Set wdDoc = New Word.Document
End Sub

Private Sub Class_Terminate()
wdDoc.Close False
Set wdDoc = Nothing
End Sub

'---'

Private Function CopyRTF(strCopyString As String) As Boolean
Dim hGlobalMemory  As Long
Dim lpGlobalMemory As Long
Dim hClipMemory    As Long
Dim lngFormatRTF   As Long

'Allocate and copy string to memory'
hGlobalMemory = GlobalAlloc(&H42, Len(strCopyString) + 1)
lpGlobalMemory = GlobalLock(hGlobalMemory)
lpGlobalMemory = lstrcpy(lpGlobalMemory, strCopyString)

'Unlock the memory and then copy to the clipboard'
If GlobalUnlock(hGlobalMemory) = 0 Then
    If OpenClipboard(0&) <> 0 Then
        Call EmptyClipboard

        'Save the data as Rich Text Format'
        lngFormatRTF = RegisterClipboardFormat("Rich Text Format")
        hClipMemory = SetClipboardData(lngFormatRTF, hGlobalMemory)

        CopyRTF = CBool(CloseClipboard)
    End If
End If
End Function

'---'

Private Function PasteRTF() As String
Dim strOutput As String

'Paste the clipboard data to the wdDoc and read the plain text result'
wdDoc.Range.Paste
strOutput = wdDoc.Range.Text

'Get rid of the new lines at the beginning and end of the document'
strOutput = Left(strOutput, Len(strOutput) - 2)
strOutput = Right(strOutput, Len(strOutput) - 2)

PasteRTF = strOutput
End Function

'---'

Public Function ParseRTF(strRTF As String) As String
If CopyRTF(strRTF) Then
    ParseRTF = PasteRTF
Else
    ParseRTF = "Error in copying to clipboard"
End If
End Function

您可以使用类似于以下代码为您的12,000个单元格中的每一个调用它:
Sub CopyParseAllRange()
Dim rngCell As Range
Dim strRTF  As String

'Create new instance of clsRTFParser'
Dim RTFParser As clsRTFParser
Set RTFParser = New clsRTFParser

For Each rngCell In Range("A1:A12000")

    'Parse the cell contents'
    strRTF = RTFParser.ParseRTF(CStr(rngCell))

    'Output to the cell one column over'
    rngCell.Offset(0, 1) = strRTF
Next
End Sub

我已经在我的计算机上使用示例RTF字符串进行了模拟。对于12,000个单元格,它只花费了两分半钟的时间,这是一个更加合理的时间范围!


你好!很抱歉耽搁了一年...当时你的回答指引了我正确的方向,所以我应该接受它。迟到总比不到好。 :) - imagodei

2
您可以尝试使用正则表达式解析每个单元格,并仅保留您需要的内容。
每个RTF控制代码以“\”开头,以空格结尾,之间没有任何额外的空格。 “{}”用于分组。如果您的文本不包含任何内容,则可以将它们删除(对于“;”也是如此)。因此,现在您只保留了文本和一些不必要的单词,例如“Arial”,“Normal”等。您可以建立字典来删除它们。经过一些调整,您将只留下所需的文本。
请查看http://www.regular-expressions.info/获取更多信息和编写RegExp的绝佳工具(RegexBuddy-不幸的是它不是免费的,但它值得这笔钱。AFAIR还有试用版)。
更新:当然,我不鼓励您为每个单元格手动执行此操作。只需迭代活动范围即可: 请参阅此线程: SO:关于在VBA中迭代单元格 就我个人而言,我会尝试这个想法:
Sub Iterate()
   For Each Cell in ActiveSheet.UsedRange.Cells
      'Do something
   Next
End Sub

如何在VBA(Excel)中使用正则表达式?

参考: Excel中的正则表达式函数VBA中的正则表达式

基本上,您需要通过COM使用VBScript.RegExp对象。


是的,这是有可能的。但如果可能的话,我真的想避免多次处理。这个表格实际上是从 SQL 数据库中导出的,我将不得不在今年末之前进行多次解析。我已经有了一个部分解决方案。我有一个可以删除大部分 RTF 格式的工作 VBA 脚本,但是会保留一些(只是为了安全起见)。然后我必须查找/替换许多奇怪的标记和内容。因此,一个完整的解决方案将非常方便。手动操作一次有点无聊。做5次甚至10次都绝对会让人神经紧张。 - imagodei
我没有提到要手动为每个单元格执行此操作。只需遍历所有单元格并使用您的自定义脚本解析每个单元格即可。我会在我的评论中更新更多想法。 - juckobee
关于多次遍历的问题。如果您将任务分成几个阶段,那么您必须在自定义单元格解析器中编写它们,并在一次遍历中运行所有阶段!我不知道您的数据,但我认为只需要一个狡猾的正则表达式就足以让您在一个阶段/遍历中完成它。 - juckobee

2
这里的一些解决方案需要引用MS Word对象库。在处理我手头的卡牌时,我找到了一个不依赖它的解决方案。它使用VBA去除RTF标记和其他无关内容,如字体表和样式表。这可能对你有所帮助。我将其应用于你的数据,除了空格之外,输出与你期望的相同。
以下是代码。
首先,检查字符串是否为字母数字。给它一个长度为1的字符串。此函数用于在此处和那里确定分隔符。
Public Function Alphanumeric(Character As String) As Boolean
   If InStr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-", Character) Then
       Alphanumeric = True
   Else
       Alphanumeric = False
   End If
End Function

接下来需要移除整个分组。我通常使用此功能来移除字体表和其他垃圾。
Public Function RemoveGroup(RTFString As String, GroupName As String) As String
    Dim I As Integer
    Dim J As Integer
    Dim Count As Integer

    I = InStr(RTFString, "{\" & GroupName)

    ' If the group was not found in the RTF string, then just return that string unchanged.
    If I = 0 Then
        RemoveGroup = RTFString
        Exit Function
    End If

    ' Otherwise, we will need to scan along, from the start of the group, until we find the end of the group.
    ' The group is delimited by { and }. Groups may be nested, so we need to count up if we encounter { and
    ' down if we encounter }. When that count reaches zero, then the end of the group has been found.
    J = I
    Do
        If Mid(RTFString, J, 1) = "{" Then Count = Count + 1
        If Mid(RTFString, J, 1) = "}" Then Count = Count - 1
        J = J + 1
    Loop While Count > 0

    RemoveGroup = Replace(RTFString, Mid(RTFString, I, J - I), "")

End Function

好的,这个函数会移除所有标签。
Public Function RemoveTags(RTFString As String) As String
    Dim L As Long
    Dim R As Long
    L = 1
    ' Search to the end of the string.
    While L < Len(RTFString)
        ' Append anything that's not a tag to the return value.
        While Mid(RTFString, L, 1) <> "\" And L < Len(RTFString)
            RemoveTags = RemoveTags & Mid(RTFString, L, 1)
            L = L + 1
        Wend
    
        'Search to the end of the tag.
        R = L + 1
        While Alphanumeric(Mid(RTFString, R, 1)) And R < Len(RTFString)
            R = R + 1
        Wend
        L = R
    Wend
End Function

我们可以按照显而易见的方式去除花括号:
Public Function RemoveBraces(RTFString As String) As String
    RemoveBraces = Replace(RTFString, "{", "")
    RemoveBraces = Replace(RemoveBraces, "}", "")
End Function

一旦你将上述函数复制到你的模块中,你就可以创建一个使用它们来去除任何不需要或不想要的内容的函数。以下代码在我的情况下完美地运行。

Public Function RemoveTheFluff(RTFString As String) As String
    RemoveTheFluff = Replace(RTFString, vbCrLf, "")
    RemoveTheFluff = RemoveGroup(RemoveTheFluff, "fonttbl")
    RemoveTheFluff = RemoveGroup(RemoveTheFluff, "colortbl")
    RemoveTheFluff = RemoveGroup(RemoveTheFluff, "stylesheet")
    RemoveTheFluff = RemoveTags(RemoveBraces(RemoveTheFluff))
End Function

希望这可以有所帮助。虽然我不建议在文字处理软件中使用此方法,但如果您希望获取数据,那么这个方法可能会有所用处。


0

您的帖子让人感觉好像每个RTF文档都存储在单个Excel单元格中。如果是这样,那么

.Net Framework RichTextBox控件解决方案

将会在2行代码内将每个单元格中的RTF转换为纯文本(在进行一些系统配置以获取正确的.tlb文件以允许引用.Net Framework之后)。将单元格值放入rtfsample中即可。

Set miracle = New System_Windows_Forms.RichTextBox
With miracle
    .RTF = rtfText
    PlainText = .TEXT
End With

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接