web-dev-qa-db-ja.com

テキスト列のRTFをプレーンテキストに一括で変換する

テーブルに約1000万行あるレガシーシステムがあります。そのテーブルにはtextタイプの列があり、それらのほとんどは標準テキストですが、約50万行にRTFマークアップがあります。 RTF形式のテキストをプレーンテキストに変換する必要があります。

私の現在の方法は、SqlDataAdapterを使用してDataTableにクエリを読み込み、winforms RichTextBoxコントロールを使用して変換を行うC#プログラムです。

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

これは小さなテーブルでうまく機能しますが、これがこのような大きなデータセット(一部のrtfファイルは埋め込み画像で数メガバイトになる可能性がある)を含むテーブルで実行する必要があったのはこれが初めてであり、OutOfMemoryを取得していますC#プログラムのエラー。

クエリをより小さなバッチに分割できることはわかっていますが、RTFの書式設定を取り除くために欠けていたより良い方法があるかどうかを確認したいと思いました。

現在のソリューションと同じことをする必要がありますが、一度にデータの小さなチャンクのみをクエリする必要がありますか、これを行うより良い方法はありますか?

7

変換するCLR関数を作成してしまいました。

私は this library を見つけ、それを微調整して、ロギングや描画メソッドなどの不要なものを削除しました。これにより、安全であるとマークすることができました。

それから私はこの小さなクラスを作りました。

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

そしてこれを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

そして、それは高速で素晴らしい働きをします!

4

Itenso RTF DLLを使用してScott Chamberlainと同じことを行いましたが、私の場合、SQL 2008R2データベースでこれをSAFEとしてマークする前に、実行する必要のあった作業がさらに多くありました。 。

まず、Scottと同様に、System.Drawingへの参照を削除する必要がありました。そのための最も簡単な方法は、参照を削除し、再コンパイルしてから、ライブラリを使用していたコードの一部を書き直すことでした。ほとんどの場合、それを使用していたVOID関数からすべてのコードを削除しただけで、Drawing/Colorオブジェクトを単に「オブジェクト」オブジェクトに変更できなかった場合。

私がしなければならないもう1つのことは、安全とマークすることもできないライブラリSystem.DirectoryServicesを参照するため、log4netへのすべての参照を削除することでした。これは少し難しいですが、一般的に私は同じアプローチをとりました。

最後に、それを行った後、SAFE CLR関数では許可されていない静的な値の設定について苦情がありました。したがって、コードを更新してすべてのStatic値をREADONLYに変更し、それが機能しました(これは、コードの「ロギング」セクションのほとんどすべてで、私はまったく気にしませんでした)。

私の最終的なCLRコードは次のようになりました。

[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;
}
2
Ian Forrest

DataTableの代わりにDataReaderを使用すると、行をすべてメモリにロードする代わりに、一度に1行ずつ処理できます。それはあなたのメモリ不足エラーをバイパスするはずです。

1
Turntwo

マークアップっぽい文字列からテキストをスクレイピングする小さなSQL関数を作成しました: http://cookingwithsql.com/index.php?option=com_content&task=view&id=65&Itemid=6

残念ながら、最大8000文字の文字列データのみを処理します。おそらく、SQL 2005以降で実行している場合は、varchar(max)を使用するように変更できます。

使用法:

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

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

クイックリファレンスとして、ここにソースを掲載します。

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
1
datagod