Регулярное выражение для извлечения текста из строки RTF - PullRequest
35 голосов
/ 09 октября 2008

Я искал способ удалить текст и строку RTF и нашел следующее регулярное выражение:

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

Однако полученная строка имеет две прямоугольные скобки "}"

До: {\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 }

После: } can u send me info for the call pls }

Есть мысли о том, как улучшить регулярное выражение?

Редактировать: Более сложная строка, такая как эта, не работает: {\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 }

Ответы [ 10 ]

53 голосов
/ 09 октября 2008

В 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

7 голосов
/ 26 августа 2010

Пока что мы также не нашли хорошего ответа на этот вопрос, кроме использования элемента управления 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.

6 голосов
/ 09 октября 2008

Я использовал это раньше, и у меня это сработало:

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

Возможно, вы захотите обрезать концы результата, чтобы избавиться от лишних пробелов.

4 голосов
/ 16 января 2013

Я сделал эту вспомогательную функцию, чтобы сделать это в 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

3 голосов
/ 15 марта 2012

Regex никогда не решит эту проблему на 100%, вам нужен анализатор. Проверьте эту реализацию в CodeProject (хотя это в C #): http://www.codeproject.com/Articles/27431/Writing-Your-Own-RTF-Converter

2 голосов
/ 24 февраля 2014

Поздний автор, но приведенное ниже регулярное выражение помогло нам с кодом RTF, который мы нашли в нашей БД (мы используем его в RDL через SSRS).

Это выражение убрало его для нашей команды. Хотя это может просто разрешить наш конкретный RTF, это может быть полезной базой для кого-то. Хотя этот веб-сайт невероятно удобен для живого тестирования.

http://regexpal.com/

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

Надеюсь, это поможет, K

2 голосов
/ 09 октября 2008

Согласно 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 можете выслать мне информацию для вызова pls \ f1 \ par }

Мне удалось исправить первую фигурную скобку, добавив знак плюс в регулярное выражение:

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

И чтобы исправить фигурную скобку в конце, я сделал это:

({\\)(.+?)(})|(\\)(.+?)(\b)|}$
                            ^
         this checks if there is a curly brace at the end

Я не очень хорошо знаю формат RTF, поэтому он может работать не во всех случаях, но он работает на вашем примере ...

1 голос
/ 09 февраля 2010

Следующее решение позволяет извлекать текст из строки RTF:

FareRule = Encoding.ASCII.GetString(FareRuleInfoRS.Data);
    System.Windows.Forms.RichTextBox rtf = new System.Windows.Forms.RichTextBox();
    rtf.Rtf = FareRule;
    FareRule = rtf.Text;
1 голос
/ 10 апреля 2009

Ни один из ответов не был достаточным, поэтому я решил использовать элемент управления RichTextBox (да, даже в приложении, отличном от Winform), чтобы извлечь текст из RTF

0 голосов
/ 03 марта 2017

Вот оператор Oracle SQL, который может убрать RTF из поля Oracle:

SELECT REGEXP_REPLACE(
    REGEXP_REPLACE(
        CONTENT,
        '\\(fcharset|colortbl)[^;]+;', ''
    ),
    '(\\[^ ]+ ?)|[{}]', ''
) TEXT
FROM EXAMPLE WHERE CONTENT LIKE '{\rtf%';

Это предназначено для данных из элементов управления форматированного текста Windows, а не файлов RTF. Ограничения:

  • \{ и \} не заменяются на { и }
  • Верхние и нижние колонтитулы не обрабатываются специально
  • Изображения и другие внедренные объекты не обрабатываются специально (не знаю, что произойдет, если встретится один из них!)

Сначала он удаляет теги \fcharset и \colourtbl, которые являются специальными, поскольку данные следуют за ними, пока не будет достигнут ;. Затем он удаляет все теги \xxx (включая один необязательный завершающий пробел), за которыми следуют все символы { и }. Это обрабатывает самые простые RTF, такие как то, что вы получаете от элемента управления rich text.

...