从RTF字符串中提取文本的正则表达式

46

我正在寻找一种从RTF字符串中删除文本的方法,然后我找到了以下的正则表达式:

我想请问这个正则表达式是如何工作的?

I was looking for a way to remove text from and RTF string and I found the following regex:

({\\)(.+?)(})|(\\)(.+?)(\b)

然而,结果字符串含有两个右尖括号 "}"

Before: {\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 MS Shell Dlg 2;}{\f1\fnil MS Shell Dlg 2;}} {\colortbl ;\red0\green0\blue0;} {\*\generator Msftedit 5.41.15.1507;}\viewkind4\uc1\pard\tx720\cf1\f0\fs20 can u send me info for the call pls\f1\par }

After: } can u send me info for the call pls }

对于如何改进正则表达式,有什么想法吗?

Edit: 这样更复杂的字符串不起作用:{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 MS Shell Dlg 2;}} {\colortbl ;\red0\green0\blue0;} {\*\generator Msftedit 5.41.15.1507;}\viewkind4\uc1\pard\tx720\cf1\f0\fs20 HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\test\\myapp\\Apps\\\{3423234-283B-43d2-BCE6-A324B84CC70E\}\par }


看起来使用Richtextbox是微软官方对这个问题的解决方案! - Marco Guignard
11个回答

65
在RTF中,{和}标记表示一组。组可以嵌套。\标记控制词的开始。控制词以空格或非字母字符结尾。控制词可以后跟数字参数,中间没有分隔符。有些控制词还接受文本参数,由';'分隔。这些控制词通常在它们自己的组中。
我认为我已经成功地创建了一个模式来处理大多数情况。
\{\*?\\[^{}]+}|[{}]|\\\n?[A-Za-z]+\n?(?:-?\d+)?[ ]?

但是在您的模式上运行时留下了一些空格。


浏览RTF规范(部分内容),我发现对于纯正则表达式的剥离器来说有很多陷阱。最明显的一个是某些组应该被忽略(标题、页脚等),而其他组应该被呈现(格式)。

我编写了一个Python脚本,应该比我上面的正则表达式更好用:

def striprtf(text):
   pattern = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I)
   # control words which specify a "destionation".
   destinations = frozenset((
      'aftncn','aftnsep','aftnsepc','annotation','atnauthor','atndate','atnicn','atnid',
      'atnparent','atnref','atntime','atrfend','atrfstart','author','background',
      'bkmkend','bkmkstart','blipuid','buptim','category','colorschememapping',
      'colortbl','comment','company','creatim','datafield','datastore','defchp','defpap',
      'do','doccomm','docvar','dptxbxtext','ebcend','ebcstart','factoidname','falt',
      'fchars','ffdeftext','ffentrymcr','ffexitmcr','ffformat','ffhelptext','ffl',
      'ffname','ffstattext','field','file','filetbl','fldinst','fldrslt','fldtype',
      'fname','fontemb','fontfile','fonttbl','footer','footerf','footerl','footerr',
      'footnote','formfield','ftncn','ftnsep','ftnsepc','g','generator','gridtbl',
      'header','headerf','headerl','headerr','hl','hlfr','hlinkbase','hlloc','hlsrc',
      'hsv','htmltag','info','keycode','keywords','latentstyles','lchars','levelnumbers',
      'leveltext','lfolevel','linkval','list','listlevel','listname','listoverride',
      'listoverridetable','listpicture','liststylename','listtable','listtext',
      'lsdlockedexcept','macc','maccPr','mailmerge','maln','malnScr','manager','margPr',
      'mbar','mbarPr','mbaseJc','mbegChr','mborderBox','mborderBoxPr','mbox','mboxPr',
      'mchr','mcount','mctrlPr','md','mdeg','mdegHide','mden','mdiff','mdPr','me',
      'mendChr','meqArr','meqArrPr','mf','mfName','mfPr','mfunc','mfuncPr','mgroupChr',
      'mgroupChrPr','mgrow','mhideBot','mhideLeft','mhideRight','mhideTop','mhtmltag',
      'mlim','mlimloc','mlimlow','mlimlowPr','mlimupp','mlimuppPr','mm','mmaddfieldname',
      'mmath','mmathPict','mmathPr','mmaxdist','mmc','mmcJc','mmconnectstr',
      'mmconnectstrdata','mmcPr','mmcs','mmdatasource','mmheadersource','mmmailsubject',
      'mmodso','mmodsofilter','mmodsofldmpdata','mmodsomappedname','mmodsoname',
      'mmodsorecipdata','mmodsosort','mmodsosrc','mmodsotable','mmodsoudl',
      'mmodsoudldata','mmodsouniquetag','mmPr','mmquery','mmr','mnary','mnaryPr',
      'mnoBreak','mnum','mobjDist','moMath','moMathPara','moMathParaPr','mopEmu',
      'mphant','mphantPr','mplcHide','mpos','mr','mrad','mradPr','mrPr','msepChr',
      'mshow','mshp','msPre','msPrePr','msSub','msSubPr','msSubSup','msSubSupPr','msSup',
      'msSupPr','mstrikeBLTR','mstrikeH','mstrikeTLBR','mstrikeV','msub','msubHide',
      'msup','msupHide','mtransp','mtype','mvertJc','mvfmf','mvfml','mvtof','mvtol',
      'mzeroAsc','mzeroDesc','mzeroWid','nesttableprops','nextfile','nonesttables',
      'objalias','objclass','objdata','object','objname','objsect','objtime','oldcprops',
      'oldpprops','oldsprops','oldtprops','oleclsid','operator','panose','password',
      'passwordhash','pgp','pgptbl','picprop','pict','pn','pnseclvl','pntext','pntxta',
      'pntxtb','printim','private','propname','protend','protstart','protusertbl','pxe',
      'result','revtbl','revtim','rsidtbl','rxe','shp','shpgrp','shpinst',
      'shppict','shprslt','shptxt','sn','sp','staticval','stylesheet','subject','sv',
      'svb','tc','template','themedata','title','txe','ud','upr','userprops',
      'wgrffmtfilter','windowcaption','writereservation','writereservhash','xe','xform',
      'xmlattrname','xmlattrvalue','xmlclose','xmlname','xmlnstbl',
      'xmlopen',
   ))
   # Translation of some special characters.
   specialchars = {
      'par': '\n',
      'sect': '\n\n',
      'page': '\n\n',
      'line': '\n',
      'tab': '\t',
      'emdash': u'\u2014',
      'endash': u'\u2013',
      'emspace': u'\u2003',
      'enspace': u'\u2002',
      'qmspace': u'\u2005',
      'bullet': u'\u2022',
      'lquote': u'\u2018',
      'rquote': u'\u2019',
      'ldblquote': u'\201C',
      'rdblquote': u'\u201D', 
   }
   stack = []
   ignorable = False       # Whether this group (and all inside it) are "ignorable".
   ucskip = 1              # Number of ASCII characters to skip after a unicode character.
   curskip = 0             # Number of ASCII characters left to skip
   out = []                # Output buffer.
   for match in pattern.finditer(text):
      word,arg,hex,char,brace,tchar = match.groups()
      if brace:
         curskip = 0
         if brace == '{':
            # Push state
            stack.append((ucskip,ignorable))
         elif brace == '}':
            # Pop state
            ucskip,ignorable = stack.pop()
      elif char: # \x (not a letter)
         curskip = 0
         if char == '~':
            if not ignorable:
                out.append(u'\xA0')
         elif char in '{}\\':
            if not ignorable:
               out.append(char)
         elif char == '*':
            ignorable = True
      elif word: # \foo
         curskip = 0
         if word in destinations:
            ignorable = True
         elif ignorable:
            pass
         elif word in specialchars:
            out.append(specialchars[word])
         elif word == 'uc':
            ucskip = int(arg)
         elif word == 'u':
            c = int(arg)
            if c < 0: c += 0x10000
            if c > 127: out.append(unichr(c))
            else: out.append(chr(c))
            curskip = ucskip
      elif hex: # \'xx
         if curskip > 0:
            curskip -= 1
         elif not ignorable:
            c = int(hex,16)
            if c > 127: out.append(unichr(c))
            else: out.append(chr(c))
      elif tchar:
         if curskip > 0:
            curskip -= 1
         elif not ignorable:
            out.append(tchar)
   return ''.join(out)

它的工作原理是解析RTF代码,并跳过任何指定了“目标”的组以及所有“可忽略”的组({\*...})。我还添加了一些特殊字符的处理。

这个解析器缺少很多功能,但对于简单的文档应该足够了。

更新:此URL已更新为在Python 3.x上运行的脚本:

https://gist.github.com/gilsondev/7c1d2d753ddb522e7bc22511cfb08676


1
不错的回答。~是非换行空格,所以如果char=='~',那么应该添加u'\u00a0'吗? - Carl G
哦,好的。你知道有哪些目的地不适用吗?我只是好奇,因为我不熟悉它。谢谢。 - Carl G
6
在此处发布C#/.Net翻译:http://chrisbenard.net/2014/08/20/Extract-Text-from-RTF-in-.Net/ - Chris Benard
感谢这个脚本。我注意到,在略微变形的RTF上,这个脚本将无法提取所有文本,例如在gist中的示例。OS X TextEdit也有问题,只有unrtf似乎能够处理来自实际(可能损坏的 ;))程序的输入。 - miku
@GilsonFilho 我尝试使用上面的脚本,但每行的第一个字符也被删除了。当我在记事本中打开rtf文件时,那些以\par开头的行就是这样。我该如何解决这个问题?谢谢! - mtryingtocode
显示剩余11条评论

7

到目前为止,我们还没有找到一个好的答案,除了使用 RichTextBox 控件:

    /// <summary>
    /// Strip RichTextFormat from the string
    /// </summary>
    /// <param name="rtfString">The string to strip RTF from</param>
    /// <returns>The string without RTF</returns>
    public static string StripRTF(string rtfString)
    {
        string result = rtfString;

        try
        {
            if (IsRichText(rtfString))
            {
                // Put body into a RichTextBox so we can strip RTF
                using (System.Windows.Forms.RichTextBox rtfTemp = new System.Windows.Forms.RichTextBox())
                {
                    rtfTemp.Rtf = rtfString;
                    result = rtfTemp.Text;
                }
            }
            else
            {
                result = rtfString;
            }
        }
        catch
        {
            throw;
        }

        return result;
    }

    /// <summary>
    /// Checks testString for RichTextFormat
    /// </summary>
    /// <param name="testString">The string to check</param>
    /// <returns>True if testString is in RichTextFormat</returns>
    public static bool IsRichText(string testString)
    {
        if ((testString != null) &&
            (testString.Trim().StartsWith("{\\rtf")))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

编辑:添加了IsRichText方法。


7

我以前用过这个方法,对我有效:

\\\w+|\{.*?\}|}

您可能想要修剪结果的两端,以消除多余的空格。

5

我用JavaScript编写了这个帮助函数,目前来看,它在简单的RTF格式移除方面效果不错。

function stripRtf(str){
    var basicRtfPattern = /\{\*?\\[^{}]+;}|[{}]|\\[A-Za-z]+\n?(?:-?\d+)?[ ]?/g;
    var newLineSlashesPattern = /\\\n/g;
    var ctrlCharPattern = /\n\\f[0-9]\s/g;

    //Remove RTF Formatting, replace RTF new lines with real line breaks, and remove whitespace
    return str
        .replace(ctrlCharPattern, "")
        .replace(basicRtfPattern, "")
        .replace(newLineSlashesPattern, "\n")
        .trim();
}

注意:

  • 我稍微修改了上面@Markus Jarderot编写的正则表达式。现在,它分两步删除新行末尾的斜杠,以避免更复杂的正则表达式。
  • .trim()仅在较新的浏览器中受支持。如果你需要支持这些功能,请参考:JavaScript中的字符串修剪?

编辑:自发布此内容以来,我已更新了正则表达式,以解决一些问题。我正在一个项目中使用它,在这里查看其上下文:https://github.com/chrismbarr/LyricConverter/blob/865f17613ee8f43fbeedeba900009051c0aa2826/scripts/parser.js#L26-L37


4

2

虽然我们是后来才加入的,但是以下正则表达式对于我们在数据库中找到的RTF代码(我们通过SSRS在RDL中使用它)很有帮助。

这个表达式已经为我们的团队删除了它。虽然它可能只解决了我们特定的RTF问题,但对于其他人来说也可能是一个有用的基础。虽然这个网站非常方便进行实时测试。

http://regexpal.com/

{\*?\\.+(;})|\s?\\[A-Za-z0-9]+|\s?{\s?\\[A-Za-z0-9]+\s?|\s?}\s?

希望这能帮到您, K

2
根据RegexPal,下面加粗的两个}是:

{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 MS Shell Dlg 2;}{\f1\fnil MS Shell Dlg 2;}} {\colortbl ;\red0\green0\blue0;} {\generator Msftedit 5.41.15.1507;}\viewkind4\uc1\pard\tx720\cf1\f0\fs20 能否请您发送通话信息?\f1\par }

我通过在正则表达式中添加加号来修复了第一个大括号:

({\\)(.+?)(}+)|(\\)(.+?)(\b)
            ^
     plus sign added here

为了修复结尾的花括号,我做了这个操作:
({\\)(.+?)(})|(\\)(.+?)(\b)|}$
                            ^
         this checks if there is a curly brace at the end

我对RTF格式不是很了解,所以这种方法可能并不适用于所有情况,但它可以在你的示例中使用......


1
以下解决方案允许您从RTF字符串中提取文本:
FareRule = Encoding.ASCII.GetString(FareRuleInfoRS.Data);
    System.Windows.Forms.RichTextBox rtf = new System.Windows.Forms.RichTextBox();
    rtf.Rtf = FareRule;
    FareRule = rtf.Text;

注意:这不适用于部分 RTF 字符串。RichTextBox 控件需要一个完整的格式良好的 RTF 输入。 - ProVega

1

所有的答案都不够充分,所以我的解决方案是使用RichTextBox控件(是的,即使在非Winform应用程序中)从RTF中提取文本。


1
这里有一条Oracle SQL语句,可以从Oracle字段中删除RTF格式:
SELECT REGEXP_REPLACE(
    REGEXP_REPLACE(
        CONTENT,
        '\\(fcharset|colortbl)[^;]+;', ''
    ),
    '(\\[^ ]+ ?)|[{}]', ''
) TEXT
FROM EXAMPLE WHERE CONTENT LIKE '{\rtf%';

这是专门针对Windows富文本控件中的数据设计的,而不是RTF文件。 限制如下:
  • \{\}没有被替换为{}
  • 页眉和页脚没有被特殊处理
  • 图片和其他嵌入对象没有被特殊处理(如果遇到其中之一,将不知道会发生什么!)
它的工作原理是首先删除\fcharset\colourtbl标签,这些标签很特殊,因为在遇到;之前都会跟随数据。然后它删除所有的\xxx标签(包括单个可选的尾随空格),然后删除所有的{}字符。这可以处理大多数简单的RTF,比如从富文本控件中获取的内容。

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