Expresión regular para extraer texto de una cadena RTF

Estaba buscando una forma de eliminar el texto de la cadena RTF y encontré la siguiente expresión regular:

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

Sin embargo, la cadena resultante tiene dos corchetes angulares rectos “}”

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 }

Después: } can u send me info for the call pls }

¿Alguna idea sobre cómo mejorar la expresión regular?

Editar: una cadena más complicada como esta no 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 }

En RTF, {y} marca un grupo. Los grupos pueden ser nesteds. \ marca el comienzo de una palabra de control. Las palabras de control terminan con un espacio o un carácter no alfabético. Una palabra de control puede tener un parámetro numérico siguiente, sin ningún delimitador en el medio. Algunas palabras de control también toman parámetros de texto, separados por ‘;’. Esas palabras de control generalmente están en sus propios grupos.

Creo que he logrado hacer un patrón que solucione la mayoría de los casos.

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

Sin embargo, deja algunos espacios cuando se ejecuta en su patrón.


Yendo a través de la especificación RTF (algo de eso), veo que hay muchas trampas para los strippers basados ​​en expresiones regulares puros. El más obvio es que algunos grupos se deben ignorar (encabezados, pies de página, etc.), mientras que otros se deben representar (formatear).

He escrito una secuencia de comandos de Python que debería funcionar mejor que mi expresión regular anterior:

 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','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) 

Funciona analizando el código RTF y omitiendo cualquier grupo que tenga un “destino” especificado, y todos los grupos “ignorables” ( {\*} ). También agregué el manejo de algunos personajes especiales.

Hay muchas características que faltan para hacer esto un analizador completo, pero debería ser suficiente para documentos simples.

ACTUALIZADO: Esta url tiene este script actualizado para ejecutarse en Python 3.x:

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

Hasta ahora, no hemos encontrado una buena respuesta a esto tampoco, aparte de usar un control 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; } } 

Editar: se agregó el método IsRichText.

Lo he usado antes y funcionó para mí:

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

Es probable que desee recortar los extremos del resultado para deshacerse de los espacios sobrantes.

Regex nunca resolverá al 100% este problema, usted necesita un analizador sintáctico. Compruebe esta implementación en CodeProject (aunque está en C #): http://www.codeproject.com/Articles/27431/Writing-Your-Own-RTF-Converter

Hice esta función auxiliar para hacer esto en JavaScript. Hasta ahora, esto ha funcionado bien para la eliminación de formato RTF simple para mí.

 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 nota:

  • Modifiqué ligeramente la expresión regular escrita por @Markus Jarderot arriba. Ahora elimina las barras al final de las nuevas líneas en dos pasos para evitar una expresión regular más compleja.
  • .trim() solo se admite en los navegadores más nuevos. Si necesita soporte para estos, vea esto: ¿ Cortar cadena en JavaScript?

EDITAR: He actualizado la expresión regular para solucionar algunos problemas que he encontrado desde que publiqué esto originalmente. Estoy usando esto en un proyecto, lo veo en contexto aquí: https://github.com/chrismbarr/LyricConverter/blob/865f17613ee8f43fbeedeba900009051c0aa2826/scripts/parser.js#L26-L37

Según RegexPal , los dos} son los que están en negrita a continuación:

{\ 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 me puede enviar información para la llamada pls \ f1 \ par }

Pude arreglar la primera llave al agregar un signo más a la expresión regular:

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

Y para arreglar el corsé al final, hice esto:

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

No conozco muy bien el formato RTF, por lo que podría no funcionar en todos los casos, pero funciona en tu ejemplo …

Colaborador tardío pero la expresión regular a continuación nos ayudó con el código RTF que encontramos en nuestra base de datos (lo estamos utilizando dentro de una RDL a través de SSRS).

Esta expresión lo eliminó para nuestro equipo. Aunque puede resolver nuestro RTF específico, puede ser una base útil para alguien. Aunque este webby es increíblemente útil para las pruebas en vivo.

http://regexpal.com/

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

Espero que esto ayude, K

Ninguna de las respuestas fue suficiente, así que mi solución fue usar el control RichTextBox (sí, incluso en una aplicación que no es Winform) para extraer texto de RTF

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

Aquí hay una statement de Oracle SQL que puede quitar RTF de un campo de Oracle:

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

Esto está diseñado para datos de controles de texto enriquecido de Windows, no archivos RTF. Las limitaciones son:

  • \{ y \} no se reemplazan por { y }
  • Los encabezados y pies de página no se manejan especialmente
  • Las imágenes y otros objetos incrustados no se manejan especialmente (¡ni idea de lo que sucederá si se encuentra uno de estos!)

Funciona eliminando primero las \fcharset y \colourtbl , que son especiales porque los datos las siguen hasta que ; es alcanzado. A continuación, elimina todas las tags \xxx (incluido un único espacio posterior opcional), seguido de todos los { y } caracteres. Esto maneja el RTF más simple, como lo que obtienes del control de texto enriquecido.