Подтвердить что ты не робот

Регулярное выражение для извлечения текста из строки RTF

Я искал способ удалить текст из строки RTF и нашел следующее regex:

({\\)(.+?)(})|(\\)(.+?)(\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 }

4b9b3361

Ответ 1

В RTF, {и} отмечает группу. Группы могут быть вложенными.\отмечает начало управляющего слова. Контрольные слова заканчиваются пробелом или несимметричным символом. Управляющее слово может иметь следующий числовой параметр, без разделителя между ними. Некоторые управляющие слова также принимают текстовые параметры, разделенные символом ';'. Эти контрольные слова обычно находятся в их собственных группах.

Я думаю, что мне удалось создать шаблон, который позаботится о большинстве случаев.

\{\*?\\[^{}]+}|[{}]|\\\n?[A-Za-z]+\n?(?:-?\d+)?[ ]?

Он оставляет несколько пробелов при запуске на вашем шаблоне.


Переходя через спецификацию RTF (некоторые из них), я вижу, что существует множество ошибок для чистых дескрипторов на основе регулярных выражений. Наиболее очевидным является то, что некоторые группы следует игнорировать (верхние и нижние колонтитулы и т.д.), В то время как другие должны отображаться (форматирование).

Я написал Python script, который должен работать лучше, чем мое регулярное выражение выше:

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 обновил этот script для запуска на Python 3.x:

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

Похоже, что использование Richtextbox является официальным ответом Microsoft на эту проблему!

Ответ 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?

EDIT: я обновил регулярное выражение, чтобы обойти некоторые проблемы, которые я нашел с момента публикации. Я использую это в проекте, см. Здесь в контексте: https://github.com/chrismbarr/LyricConverter/blob/865f17613ee8f43fbeedeba900009051c0aa2826/scripts/parser.js#L26-L37

Ответ 7

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

Ответ 8

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

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

http://regexpal.com/

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

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

Ответ 9

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

Ответ 10

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

FareRule = Encoding.ASCII.GetString(FareRuleInfoRS.Data);
    System.Windows.Forms.RichTextBox rtf = new System.Windows.Forms.RichTextBox();
    rtf.Rtf = FareRule;
    FareRule = rtf.Text;

Ответ 11

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

Ответ 12

Не ломайте голову, разбирая RTF! Просто конвертируйте RTF в DOCX, затем используйте Python с модулем python-docx; Очень просто!

Лучший конвертер, который я нашел, и я оценил МНОГО, это бесплатное ПО Polenter MultiDoc Converter. http://www.multidoc-converter.com/en/index.html Он имеет пакетный режим и возможность запуска подкаталогов.