Expressão regular para extrair texto de uma string RTF

Eu estava procurando uma maneira de remover texto e seqüência de caracteres RTF e encontrei o seguinte regex:

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

No entanto, a string resultante tem dois colchetes angulares “}”

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

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

Alguma idéia sobre como melhorar o regex?

Editar: Uma seqüência de caracteres mais complicada como este não funciona: {\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 }

    Em RTF, {e} marca um grupo. Grupos podem ser nesteds. \ marca o início de uma palavra de controle. As palavras de controle terminam com um espaço ou um caractere não alfabético. Uma palavra de controle pode ter um parâmetro numérico a seguir, sem qualquer delimitador entre elas. Algumas palavras de controle também usam parâmetros de texto, separados por ‘;’. Essas palavras de controle geralmente estão em seus próprios grupos.

    Acho que consegui fazer um padrão que cuida da maioria dos casos.

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

    Deixa alguns espaços quando executado em seu padrão embora.


    Indo através da especificação RTF (alguns deles), vejo que há muitas armadilhas para strippers baseados em regex pura. O mais óbvio é que alguns grupos devem ser ignorados (headers, rodapés, etc.), enquanto outros devem ser renderizados (formatação).

    Eu escrevi um script Python que deve funcionar melhor do que o meu regex acima:

     def striprtf(text): pattern = re.compile(r"\\([az]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^az])|([{}])|[\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','mmodsumppedname','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) 

    Ele funciona analisando o código RTF e ignorando quaisquer grupos que tenham um “destino” especificado e todos os grupos “ignoráveis” ( {\*} ). Eu também adicionei o manuseio de alguns caracteres especiais.

    Há muitos resources ausentes para tornar isso um analisador completo, mas deve ser suficiente para documentos simples.

    ATUALIZADO: Este URL tem este script atualizado para rodar em Python 3.x:

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

    Até agora, não encontramos uma boa resposta para isso, além de usar um controle RichTextBox:

      ///  /// Strip RichTextFormat from the string ///  /// The string to strip RTF from /// The string without RTF 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; } ///  /// Checks testString for RichTextFormat ///  /// The string to check /// True if testString is in RichTextFormat public static bool IsRichText(string testString) { if ((testString != null) && (testString.Trim().StartsWith("{\\rtf"))) { return true; } else { return false; } } 

    Edit: Adicionado o método IsRichText.

    Eu usei isso antes e funcionou para mim:

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

    Você provavelmente vai querer cortar as extremidades do resultado para se livrar dos espaços extras que sobraram.

    Regex não irá nunca 100% resolver este problema, você precisa de um analisador. Verifique esta implementação no CodeProject (é em C # embora): http://www.codeproject.com/Articles/27431/Writing-Your-Own-RTF-Converter

    Eu fiz essa function auxiliar para fazer isso em JavaScript. Até agora, isso funcionou bem para a simples remoção de formatação RTF para mim.

     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(); } 

    De importância:

    • Eu modifiquei ligeiramente o regex escrito por @Markus Jarderot acima. Ele agora remove barras no final de novas linhas em duas etapas para evitar um regex mais complexo.
    • .trim() é suportado apenas em navegadores mais recentes. Se você precisar de suporte para isso, veja isto: Trim string in JavaScript?

    EDIT: Eu atualizei o regex para contornar alguns problemas que encontrei desde postar isso originalmente. Estou usando isso em um projeto, veja-o no contexto aqui: https://github.com/chrismbarr/LyricConverter/blob/865f17613ee8f43fbeedeba900009051c0aa2826/scripts/parser.js#L26-L37

    De acordo com o RegexPal , os dois} são os que estão em negrito abaixo:

    {\ 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 você pode me enviar informações para a chamada pls \ f1 \ par }

    Consegui consertar a primeira chave, adicionando um sinal de mais ao regex:

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

    E para consertar a chave no final, fiz isso:

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

    Eu não sei o formato RTF muito bem, então isso pode não funcionar em todos os casos, mas funciona no seu exemplo …

    O colaborador tardio, mas a regex abaixo, nos ajudou com o código RTF que encontramos em nosso database (estamos usando em um RDL via SSRS).

    Essa expressão removeu para nossa equipe. Embora possa apenas resolver nosso RTF específico, pode ser uma base útil para alguém. Embora este webby é incrível acessível para testes ao vivo.

    http://regexpal.com/

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

    Espero que isso ajude, K

    Nenhuma das respostas foi suficiente, então minha solução foi usar o controle RichTextBox (sim, mesmo em um aplicativo não-WinForm) para extrair texto do RTF

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

    Aqui está uma instrução SQL do Oracle que pode extrair RTF de um campo Oracle:

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

    Isso é projetado para dados de controles rich text do Windows, não de arquivos RTF. Limitações são:

    • \{ e \} não são substituídos por { e }
    • Cabeçalhos e rodapés não são tratados especialmente
    • Imagens e outros objects incorporados não são tratados especialmente (não há idéia do que acontecerá se um deles for encontrado!)

    Ele funciona primeiro removendo as \fcharset e \colourtbl , que são especiais porque os dados as seguem até ; é atingido. Em seguida, ele remove todas as tags \xxx (incluindo um único espaço final opcional), seguido por todos os caracteres { e } . Isso lida com o RTF mais simples, como o que você obtém do controle rich text.