Olá pessoal,
Tudo bem?
Nesse artigo, vou demonstrar pra vocês como converter uma string RTF para texto (Remover tags RTF) utilizando o CLR (C#) ou Powershell, que foi uma necessidade que tive semana passada, onde um sistema gravava as informações em uma tabela e os dados eram no formato RTF (Rich Text Format). Pesquisei bastante na internet para encontrar soluções que me ajudassem a converter o RTF para texto direto pelo banco de dados e não encontrei muitas alternativas do meu agrado e isso me motivou a escrever esse post para vocês.
Caso você não conheça o CLR ou quer saber mais sobre ele, entender como ele funciona, suas limitações, vantagens, desvantagens e como criar e publicar um novo projeto CLR, dê uma lida no post Introdução ao SQL CLR (Common Language Runtime) no SQL Server.
A solução trivial com C#
Se você fizer essa pesquisa no Google, provavelmente a solução mais comum que você vai encontrar, é essa:
System.Windows.Forms.RichTextBox rtBox = new System.Windows.Forms.RichTextBox();
string rtfText = "{\rtf1\ansi\ansicpg1252\fromhtml1 \fbidis \deff0{\fonttbl {\f0\fswiss Arial;} {\f1\fmodern Courier New;} {\f2\fnil\fcharset2 Symbol;} {\f3\fmodern\fcharset0 Courier New;} {\f4\fswiss Segoe UI;}} {\colortbl\red0\green0\blue0;\red0\green0\blue255;\red0\green0\blue0;} \uc1\pard\plain\deftab360 \f0\fs24 {\*\htmltag0 \par } {\*\htmltag48 }{\*\htmltag64}\htmlrtf {\htmlrtf0 {\*\htmltag148 }\htmlrtf {\cf2 \fs20 \f4 \htmlrtf0 {\*\htmltag4 \par }\htmlrtf }\htmlrtf0 {\*\htmltag72}\htmlrtf}\htmlrtf0 {\*\htmltag96 }\htmlrtf {\ltrpar\ltrch\ql \htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \htmlrtf {\cf2 \fs20 \f4 \htmlrtf0 Dirceu Resende\htmlrtf }\htmlrtf0 {\*\htmltag72}\htmlrtf\par}\htmlrtf0 {\*\htmltag104 }\htmlrtf }\htmlrtf0 {\*\htmltag248 } {\*\htmltag58 }}";
rtBox.Rtf = rtfText;
string plainText = rtBox.Text;
Que é uma solução realmente bem simples e funciona mesmo. Entretanto, eu gostaria de utilizar essa solução pelo banco de dados e por isso, utilizar a biblioteca System.Windows.Forms não é uma ideia muito interessante, visto que essa biblioteca não é carregada por padrão pelo SQL Server e por isso, eu teria que importar o assembly dela e suas dependências (que não são poucas) manualmente no banco de dados, conforme demonstrado abaixo:
Isso aumentaria a complexidade do seu CLR (e também nas publicações) e pode trazer efeitos negativos para a estabilidade da sua instância e por isso, não recomendo essa prática.
A solução com C# utilizando o Helper RichTextStripper
Depois de muitas pesquisas na internet de soluções parecidas, encontrei uma classe chamada RichTextStripper, escrita em C# e desenvolvida pelo Chris Benard, que faz exatamente isso. Ele encontrou um código Python que faz esse trabalho e converteu o código para C#.
Código-fonte da função no CLR
Essa é a função que será gerada no banco de dados e você irá utilizá-la para remover a formatação RTF de strings e colunas no SQL Server, após a publicação do seu CLR e utilizando a classe RichTextStripper.
using System.Collections.Generic;
using System.Text.RegularExpressions;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static string fncRemove_RTF_String(string Ds_String_Rtf)
{
return RichTextStripper.StripRichTextFormat(Ds_String_Rtf).Trim();
}
}
/// <summary>
/// Rich Text Stripper
/// </summary>
/// <remarks>
/// Translated from Python located at:
/// http://stackoverflow.com/a/188877/448
/// https://chrisbenard.net/2014/08/20/extract-text-from-rtf-in-c-net/
/// </remarks>
public static class RichTextStripper
{
private class StackEntry
{
public int NumberOfCharactersToSkip { get; set; }
public bool Ignorable { get; set; }
public StackEntry(int numberOfCharactersToSkip, bool ignorable)
{
NumberOfCharactersToSkip = numberOfCharactersToSkip;
Ignorable = ignorable;
}
}
private static readonly Regex _rtfRegex = new Regex(@"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
private static readonly List<string> destinations = new List<string>
{
"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"
};
private static readonly Dictionary<string, string> specialCharacters = new Dictionary<string, string>
{
{ "par", "\n" },
{ "sect", "\n\n" },
{ "page", "\n\n" },
{ "line", "\n" },
{ "tab", "\t" },
{ "emdash", "\u2014" },
{ "endash", "\u2013" },
{ "emspace", "\u2003" },
{ "enspace", "\u2002" },
{ "qmspace", "\u2005" },
{ "bullet", "\u2022" },
{ "lquote", "\u2018" },
{ "rquote", "\u2019" },
{ "ldblquote", "\u201C" },
{ "rdblquote", "\u201D" },
};
/// <summary>
/// Strip RTF Tags from RTF Text
/// </summary>
/// <param name="inputRtf">RTF formatted text</param>
/// <returns>Plain text from RTF</returns>
public static string StripRichTextFormat(string inputRtf)
{
if (inputRtf == null)
{
return null;
}
string returnString;
var stack = new Stack<StackEntry>();
var ignorable = false; // Whether this group (and all inside it) are "ignorable".
var ucskip = 1; // Number of ASCII characters to skip after a unicode character.
var curskip = 0; // Number of ASCII characters left to skip
var outList = new List<string>(); // Output buffer.
var matches = _rtfRegex.Matches(inputRtf);
if (matches.Count > 0)
{
foreach (Match match in matches)
{
var word = match.Groups[1].Value;
var arg = match.Groups[2].Value;
var hex = match.Groups[3].Value;
var character = match.Groups[4].Value;
var brace = match.Groups[5].Value;
var tchar = match.Groups[6].Value;
if (!string.IsNullOrEmpty(brace))
{
curskip = 0;
if (brace == "{")
{
// Push state
stack.Push(new StackEntry(ucskip, ignorable));
}
else if (brace == "}")
{
// Pop state
var entry = stack.Pop();
ucskip = entry.NumberOfCharactersToSkip;
ignorable = entry.Ignorable;
}
}
else if (!string.IsNullOrEmpty(character)) // \x (not a letter)
{
curskip = 0;
if (character == "~")
{
if (!ignorable)
{
outList.Add("\xA0");
}
}
else if ("{}\\".Contains(character))
{
if (!ignorable)
{
outList.Add(character);
}
}
else if (character == "*")
{
ignorable = true;
}
}
else if (!string.IsNullOrEmpty(word)) // \foo
{
curskip = 0;
if (destinations.Contains(word))
{
ignorable = true;
}
else if (ignorable)
{
}
else if (specialCharacters.ContainsKey(word))
{
outList.Add(specialCharacters[word]);
}
else if (word == "uc")
{
ucskip = int.Parse(arg);
}
else if (word == "u")
{
var c = int.Parse(arg);
if (c < 0)
{
c += 0x10000;
}
outList.Add(char.ConvertFromUtf32(c));
curskip = ucskip;
}
}
else if (!string.IsNullOrEmpty(hex)) // \'xx
{
if (curskip > 0)
{
curskip -= 1;
}
else if (!ignorable)
{
var c = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
outList.Add(char.ConvertFromUtf32(c));
}
}
else if (!string.IsNullOrEmpty(tchar))
{
if (curskip > 0)
{
curskip -= 1;
}
else if (!ignorable)
{
outList.Add(tchar);
}
}
}
}
else
{
// Didn't match the regex
returnString = inputRtf;
}
returnString = string.Join(string.Empty, outList.ToArray());
return returnString;
}
}
Demonstração de uso da função
Como vocês podem observar abaixo, o resultado da função é o mesmo que você vai visualizar quando abrir o documento (ou string) no Microsoft Word.
A solução com Powershell
Se você não quer utilizar o CLR (C#) para realizar essa tarefa, não se preocupe. Também é possível fazer isso utilizando Powershell (Posh) e você pode integrá-lo com o SQL Server de várias maneiras utilizando o xp_cmdshell e exportando o resultado para texto ou capturar o retorno do powershell.
O script do Powershell é bem simples. Lembra aquele script simples do C# que postei no começo do post e que eu não queria implementar no CLR pro causa das dependências do Windows.Forms? Então.. No powershell isso não é problema.. 🙂
Código-fonte do Powershell:
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
$Rtb = New-Object -TypeName System.Windows.Forms.RichTextBox
$stringRTF = "{\rtf1\ansi\ansicpg1252\fromhtml1 \fbidis \deff0{\fonttbl {\f0\fswiss Arial;} {\f1\fmodern Courier New;} {\f2\fnil\fcharset2 Symbol;} {\f3\fmodern\fcharset0 Courier New;} {\f4\fswiss Segoe UI;}} {\colortbl\red0\green0\blue0;\red0\green0\blue255;\red0\green0\blue0;} \uc1\pard\plain\deftab360 \f0\fs24 {\*\htmltag0 \par } {\*\htmltag48 }{\*\htmltag64}\htmlrtf {\htmlrtf0 {\*\htmltag148 }\htmlrtf {\cf2 \fs20 \f4 \htmlrtf0 {\*\htmltag4 \par }\htmlrtf }\htmlrtf0 {\*\htmltag72}\htmlrtf}\htmlrtf0 {\*\htmltag96 }\htmlrtf {\ltrpar\ltrch\ql \htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \htmlrtf {\cf2 \fs20 \f4 \htmlrtf0 Dirceu Resende\htmlrtf }\htmlrtf0 {\*\htmltag72}\htmlrtf\par}\htmlrtf0 {\*\htmltag104 }\htmlrtf }\htmlrtf0 {\*\htmltag248 } {\*\htmltag58 }}"
$Rtb.Rtf = $stringRTF
$Retorno = $Rtb.Text
Write-Host($Retorno)
Resultado da execução do Powershell
Exemplo 1:
Com isso, você pode utilizar o xp_cmdshell para executar o seu Powershell conforme a sua necessidade:

Exemplo utilizando o -File do PowerShell e informando o caminho de um arquivo que criei previamente com os comandos Posh demonstrados acima
Exemplo 2
DECLARE
@StringRTF VARCHAR(MAX),
@Posh VARCHAR(8000)
SET @StringRTF = '{\rtf1\ansi\ansicpg1252\fromhtml1 \fbidis \deff0{\fonttbl {\f0\fswiss Arial;} {\f1\fmodern Courier New;} {\f2\fnil\fcharset2 Symbol;} {\f3\fmodern\fcharset0 Courier New;} {\f4\fswiss Segoe UI;}} {\colortbl\red0\green0\blue0;\red0\green0\blue255;\red0\green0\blue0;} \uc1\pard\plain\deftab360 \f0\fs24 {\*\htmltag0 \par } {\*\htmltag48 }{\*\htmltag64}\htmlrtf {\htmlrtf0 {\*\htmltag148 }\htmlrtf {\cf2 \fs20 \f4 \htmlrtf0 {\*\htmltag4 \par }\htmlrtf }\htmlrtf0 {\*\htmltag72}\htmlrtf}\htmlrtf0 {\*\htmltag96 }\htmlrtf {\ltrpar\ltrch\ql \htmlrtf0 {\*\htmltag64}\htmlrtf {\htmlrtf0 \htmlrtf {\cf2 \fs20 \f4 \htmlrtf0 Dirceu Resende\htmlrtf }\htmlrtf0 {\*\htmltag72}\htmlrtf\par}\htmlrtf0 {\*\htmltag104 }\htmlrtf }\htmlrtf0 {\*\htmltag248 } {\*\htmltag58 }}'
SET @Posh = 'powershell.exe -ExecutionPolicy ByPass -Command "Add-Type -AssemblyName System.Windows.Forms; $Rtb = New-Object -TypeName System.Windows.Forms.RichTextBox; $stringRTF = ''' + @StringRTF + '''; $Rtb.Rtf = $stringRTF; $Retorno = $Rtb.Text; Write-Host($Retorno);"'
IF (OBJECT_ID('tempdb..#Retorno') IS NOT NULL) DROP TABLE #Retorno
CREATE TABLE #Retorno (
Texto VARCHAR(MAX)
)
INSERT INTO #Retorno
EXEC master.dbo.xp_cmdshell @Posh
SELECT *
FROM #Retorno
Resultado:
É isso aí, pessoal!
Espero que tenham gostado do post e até a próxima.
sql server clr converter convert remover remove rdf rich text format tags strings para to texto simples plain text c# csharp powershell posh ps
sql server clr converter convert remover remove rdf rich text format tags strings para to texto simples plain text c# csharp powershell posh ps





Comentários (0)
Carregando comentários…