Konvertieren von RTF in einer Textspalte in Bulk-Text in großen Mengen

8

Ich habe ein Legacy-System mit ungefähr 10 Millionen Zeilen in einer Tabelle. In dieser Tabelle gibt es eine Typenspalte text, die meisten davon sind Standardtext, aber ungefähr 500.000 Zeilen enthalten RTF-Markups. Ich muss den RTF-formatierten Text in einfachen Text konvertieren.

Meine aktuelle Methode ist, dass ich ein C # -Programm habe, das die Abfrage mit a in eine DataTable lädt SqlDataAdapterund das Winforms- RichTextBoxSteuerelement verwendet, um die Konvertierung durchzuführen .

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    count = 0;

    rtbRTFToPlain = new RichTextBox();

    using (SqlDataAdapter ada = new SqlDataAdapter("select note_guid, notes from client_notes", Globals.SQLConnectionString))
    using(SqlCommandBuilder cmb = new SqlCommandBuilder(ada))
    {
        DataTable dt = new DataTable();
        ada.UpdateCommand = cmb.GetUpdateCommand();

        ada.Fill(dt);

        int reportEvery = dt.Rows.Count / 100;
        if (reportEvery == 0)
            reportEvery = 1;
        foreach (DataRow row in dt.Rows)
        {
            if (count % reportEvery == 0)
                bw.ReportProgress(count / reportEvery);

            try
            {
                if (((string)row["notes"]).TrimStart().StartsWith("{") == true)
                {
                    rtbRTFToPlain.Rtf = (string)row["notes"];
                    row["notes"] = rtbRTFToPlain.Text;
                }
            }
            catch
            {
            }

            count++;

        }
        bw.ReportProgress(100);

        this.Invoke(new Action(() => 
            {
                this.ControlBox = false;
                this.Text = "Updating database please wait";
            }));
        ada.Update(dt);
    }
}

Dies funktioniert hervorragend für kleine Tabellen, aber dies ist das erste Mal, dass ich es auf einer Tabelle mit einem so großen Datensatz ausführen musste (einige der RTF-Dateien können mit eingebetteten Bildern mehrere Megabyte groß sein), und ich erhalte OutOfMemory Fehler mit meinem C # -Programm.

Ich weiß, dass ich meine Abfrage in kleinere Stapel aufteilen kann, aber ich wollte herausfinden, ob es einen besseren Weg gibt, den ich vermisst habe, um die RTF-Formatierung zu entfernen.

Sollte ich genau das Gleiche tun wie meine aktuelle Lösung, aber immer nur kleinere Datenblöcke abfragen, oder gibt es einen besseren Weg, dies zu tun?

Scott Chamberlain
quelle

Antworten:

5

Am Ende habe ich eine CLR-Funktion erstellt, um sie zu konvertieren.

Ich habe diese Bibliothek gefunden und sie dann ein wenig optimiert, um Dinge zu entfernen, die ich nicht brauchte, wie Protokollierungs- und Zeichenmethoden, wodurch ich sie als sicher markieren konnte.

Ich habe dann gerade diese kleine Klasse gemacht.

using System.Data.SqlTypes;
using Itenso.Rtf.Converter.Text;
using Itenso.Rtf.Support;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlString RtfToPlainText(SqlString text)
    {
        if (text.Value.StartsWith(@"{\rtf"))
        {
            RtfTextConverter textConverter = new RtfTextConverter();
            RtfInterpreterTool.Interpret(text.Value, textConverter);
            return textConverter.PlainText;
        }
        else
            return text;
    }
}

Und lief dies in SQL

sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO

CREATE ASSEMBLY ConversionsSqlExtensionsAssembly 
from 'E:\Code\ConversionsSqlExtensions\bin\Debug\ConversionsSqlExtensions.dll' 
WITH PERMISSION_SET = safe
go

CREATE function RtfToPlainText(@value nvarchar(max))
returns nvarchar(max)
AS EXTERNAL NAME ConversionsSqlExtensionsAssembly.StoredProcedures.RtfToPlainText

Und es ist schnell und funktioniert super!

Scott Chamberlain
quelle
1
Ihre Lösung sieht wirklich gut aus. Ich versuche erfolglos, Protokollierungs- und Zeichenmethoden aus der RTF-Bibliothek zu entfernen. Kann ich Sie nach der geänderten Bibliothek fragen? Danke Daniel Eyer
@ user22279 In der Antwort von Ian finden Sie sehr ähnliche Schritte wie bei meiner Ausführung , damit die Bibliothek als "SICHER" gekennzeichnet werden kann.
Scott Chamberlain
2

Ich habe das Gleiche wie Scott Chamberlain mit der Itenso RTF-DLL gemacht, aber in meinem Fall musste noch viel mehr Arbeit geleistet werden, bevor dies in meiner SQL 2008R2-Datenbank als SICHER markiert werden konnte.

Zuerst musste ich wie Scott den Verweis auf System.Drawing entfernen. Ich fand den einfachsten Weg, dies zu tun, indem ich die Referenz entfernte, neu kompilierte und dann die Codebits neu schrieb, die die Bibliothek verwendeten. In den meisten Fällen habe ich nur den gesamten Code aus den VOID-Funktionen entfernt, die ihn verwendet haben, und in Situationen, in denen ich Zeichnungs- / Farbobjekte nicht in "Objekt" -Objekte ändern konnte.

Das andere, was ich tun musste, war, alle Verweise auf log4net zu entfernen, da es auf eine Bibliothek System.DirectoryServices verweist, die auch nicht als sicher markiert werden kann. Das war etwas schwieriger, aber im Allgemeinen habe ich den gleichen Ansatz gewählt.

Nachdem ich das getan hatte, bekam ich Beschwerden über das Festlegen statischer Werte, was in einer SAFE CLR-Funktion nicht zulässig ist. Also habe ich den Code aktualisiert, um alle statischen Werte auf READONLY zu ändern, und das hat funktioniert (dies war so ziemlich alles im Abschnitt "Protokollierung" des Codes, der mir in keiner Weise wirklich wichtig war).

Mein endgültiger CLR-Code sah folgendermaßen aus:

[Microsoft.SqlServer.Server.SqlFunction]
[return: SqlFacet(MaxSize=-1)]
public static SqlChars RTFFix([SqlFacet(MaxSize=-1)]string rtfField)
{
    SqlChars returnChars;
    try
    {

        RtfTextConverter textConverter = new RtfTextConverter();
        RtfInterpreterTool.Interpret(rtfField, textConverter);
        returnChars = new SqlChars(new SqlString(textConverter.PlainText.Trim()));
    }
    catch (Exception e)
    {
        returnChars = new SqlChars(new SqlString(rtfField));
    }
    return returnChars;
}
Ian Forrest
quelle
Vielen Dank, dass Sie eine Antwort veröffentlicht haben, in der detailliert beschrieben wird, wie eine Bibliothek in eine sichere Bibliothek konvertiert wird. Ich wollte nicht mitteilen, was ich in meiner Antwort getan habe, da die von mir geänderte Bibliothek GPL-lizenziert war (sie scheint jetzt COPL-lizenziert zu sein), und ich befürchtete, dass das Teilen von Änderungen "verteilen" könnte, was mich dazu zwingen würde, viel mehr zu tun unter der GPL.
Scott Chamberlain
1

Ich habe eine kleine SQL-Funktion geschrieben, die den Text aus markup-artigen Zeichenfolgen entfernt: http://cookingwithsql.com/index.php?option=com_content&task=view&id=65&Itemid=60

Leider werden nur Zeichenfolgendaten mit bis zu 8000 Zeichen verarbeitet. Möglicherweise könnte es geändert werden, um varchar (max) zu verwenden, wenn Sie mit SQL 2005 und höher arbeiten.

Verwendungszweck:

select dbo.ScrapeText('<I love SQL> gobbldygook font 12 blah blah') as 'Result'

Result
----------------------------
I love SQL

Ich werde die Quelle hier als Kurzreferenz veröffentlichen.

use master
IF (object_id('dbo.ScrapeText') IS NOT NULL)
BEGIN
  PRINT 'Dropping: dbo.ScrapeText'
  DROP function dbo.ScrapeText
END
GO
PRINT 'Creating: dbo.ScrapeText'
GO
CREATE FUNCTION dbo.ScrapeText 
(
  @string varchar(8000)
) 
returns varchar(8000)

AS
BEGIN
---------------------------------------------------------------------------------------------------
-- Title:        ScrapeText
--               
-- Date Created: April 4, 2006
--               
-- Author:       William McEvoy
--               
-- Description:  This function will attempt to remove markup language formatting from a string. This is 
--               accomplished by concetenating all text contained between greater than and less 
--               than signs within the formatted text.  
--               
---------------------------------------------------------------------------------------------------
-- Date Revised: 
-- Author:       
-- Reason:       
---------------------------------------------------------------------------------------------------

declare @text  varchar(8000),
        @PenDown char(1),
        @char  char(1),
        @len   int,
        @count int

select  @count = 0,
        @len   = 0,
        @text  = ''


---------------------------------------------------------------------------------------------------
-- M A I N   P R O C E S S I N G
---------------------------------------------------------------------------------------------------

-- Add tokens
select @string = '>' + @string + '<'

-- Replace Special Characters
select @string = replace(@string,' ',' ')

-- Parse out the formatting codes
select @len = len(@string)
while (@count <= @len)
begin
  select @char = substring(@string,@count,1)

  if (@char = '>')
     select @PenDown = 'Y'
  else 
  if (@char = '<')
    select @PenDown = 'N'
  else  
  if (@PenDown = 'Y')
    select @text = @text + @char

  select @count = @count + 1
end

RETURN @text
END
GO
IF (object_id('dbo.ScrapeText') IS NOT NULL)
  PRINT 'Function created.'
ELSE
  PRINT 'Function NOT created.'
GO
Datagod
quelle
Während dies für HTML-Text funktioniert, ist diese Lösung für RTF nutzlos.
Scott Chamberlain
1

Wenn Sie einen DataReader anstelle einer DataTable verwenden, können Sie die Zeilen einzeln verarbeiten, anstatt alles in den Speicher zu laden. Das sollte Ihre Speicherfehler umgehen.

Turntwo
quelle