如何在 Firemonkey 中将 HTML 格式的文本作为 HTML 复制到剪贴板

Hil*_*are 2 delphi clipboard copy-paste firemonkey

使用 Delphi FireMonkey,我需要将 HTML 格式的字符串复制到 Delphi 中的剪贴板,以便粘贴它的其他应用程序会将其视为 HTML。

我已经尝试通过隐藏的备忘录组件复制简单文本,它可以工作,但被复制为简单文本。我需要 HTML 格式的副本。将其作为文本粘贴到任何富文本程序中时,它会显示所有 HTML 标记而不是格式化文本。

我当前将文本复制到剪贴板的方法是:

procedure TForm1.Button1Click(Sender: TObject);
var
  SourceText: string;
begin
  SourceText := 'This is a <b>bold</b> html line';
  //I can use TMemo to copy it to clipboard like:
  Memo1.lines.Text := SourceText;
  Memo1.SelectAll;
  memo1.CopyToClipboard;
end;
Run Code Online (Sandbox Code Playgroud)

但问题是,如果我将剪贴板上复制的文本粘贴到 Microsoft Word 中,它将被粘贴为:

  • “这是一个<b>bold</b>html 行”。我想要并且应该是这样的:

  • 这是一个粗体的html 行

注意:我已经阅读了有关如何在 Windows 中完成此操作的其他讨论,但我需要一个用于跨平台应用程序的 Firemonkey 解决方案。任何帮助表示赞赏。

fpi*_*tte 7

FireMonkey跨平台复制数据到剪贴板是通过IFMXExtendedClipboardService调用平台服务获得的接口完成的:

var
    Svc      : IFMXExtendedClipboardService;
begin
    if not TPlatformServices.Current.SupportsPlatformService(IFMXExtendedClipboardService, Svc) then 
        Exit;  // Not clipboard supported
    // Code using the interface
Run Code Online (Sandbox Code Playgroud)

该接口具有将文本/图像复制到剪贴板/从剪贴板获取文本/图像的方法:SetText, GetText, SetImage, GetImage

对于其他类型的数据,用户必须注册数据格式,然后向剪贴板写入数据/从剪贴板读取数据:RegisterCustomFormat, IsCustomFormatRegistered, UnregisterCustomFormat, HasCustomFormat, GetCustomFormat, SetCustomFormat

在您的问题中,您说要将 HTML 格式的数据复制到剪贴板。这可以转换为注册 HTML 格式、使用数据创建流并调用 SetCustomFormat 传递格式和流。

使用上述接口方法可以使用任何格式并将其传输到剪贴板或从剪贴板传输。下面的代码获取流stream并使用以下命令将其内容复制到剪贴板ClipFormat

if TPlatformServices.Current.SupportsPlatformService(
                IFMXExtendedClipboardService, Svc) then begin
    if not Svc.IsCustomFormatRegistered(ClipFormat) then
        Svc.RegisterCustomFormat(ClipFormat);
    Svc.SetCustomFormat(ClipFormat, Stream);
end;
Run Code Online (Sandbox Code Playgroud)

格式必须仅注册一次,因此调用IsCustomFormatRegistered以防止RegisterCustomFormat多次调用。

格式的名称是一个简单的字符串,可以是任何内容。复制和粘贴应用程序必须就格式名称和数据格式(数据写入流中的方式)达成一致。

由于样式的原因,编写 HTML 格式的数据可能会很困难。由于样式的原因,仅获取完整 HTML 文档的简单副本可能无法正确呈现。

如果您通过剪贴板在两个应用程序之间传输数据,您可以做任何您想做的事情。但是在您的应用程序和另一个应用程序(您在问题中提到了 Microsoft Word)之间传输数据要困难得多。

在 Microsoft 产品和 Windows 平台上的所有其他产品中,剪贴板中的 HTML 格式如下所述

一旦格式正确,您在问题中给出的示例如下所示:

Version:0.9
StartHTML:00000144
EndHTML:00000218
StartFragment:00000167
EndFragment:00000205
StartSelection:00000167
EndSelection:00000205
<!DOCTYPE><HTML><BODY><P>This is a <b>bold</b> html line</P></BODY></HTML>
Run Code Online (Sandbox Code Playgroud)

实际的字符串如上,每行末尾有一个 CRLF。格式名称是“HTML 格式”。

您会看到该句子This is a <b>bold</b> html line必须被 HTML 标记包围才能形成有效的完整 HTML 文档,并且前面有由多个对组成的标题keyword:value。如果是字符串,则值会偏移。关键字是非常不言自明的。该字符串是 UTF8,如果您使用 HTML 实体来表示特殊字符,则可以将其简化为 ANSI。

我编写了一个函数来构建整个字符串:

function FormatHtmlForClipboard(const HtmlSrc : UTF8String) : UTF8String;
const
    Header = 'Version:0.9'             + #13#10 +
             'StartHTML:00000000'      + #13#10 +
             'EndHTML:00000000'        + #13#10 +
             'StartFragment:00000000'  + #13#10 +
             'EndFragment:00000000'    + #13#10 +
             'StartSelection:00000000' + #13#10 +
             'EndSelection:00000000'   + #13#10;
var
    BodyStart : Integer;
    BodyEnd   : Integer;
    HdrLen    : Integer;
begin
    Result := Header;
    BodyStart := Pos('<BODY>', String(HtmlSrc));
    if BodyStart <= 0 then
        raise Exception.Create('<BODY> tag not found');
    Inc(BodyStart, 6);
    BodyEnd := Pos('</BODY>', String(HtmlSrc));
    if BodyEnd <= 0 then
        raise Exception.Create('</BODY> tag not found');

    HdrLen := Length(Header) - 1;
    WriteNumberIntoString(HdrLen,                   'StartHTML:',      Result);
    WriteNumberIntoString(HdrLen + Length(HtmlSrc), 'EndHTML:',        Result);
    WriteNumberIntoString(HdrLen + BodyStart,       'StartFragment:',  Result);
    WriteNumberIntoString(HdrLen + BodyEnd,         'EndFragment:',    Result);
    WriteNumberIntoString(HdrLen + BodyStart,       'StartSelection:', Result);
    WriteNumberIntoString(HdrLen + BodyEnd,         'EndSelection:',   Result);
    Result := Result + HtmlSrc;
end;

procedure WriteNumberIntoString(
    N        : Integer;
    const At : UTF8String;
    var S    : UTF8String);
var
    I : Integer;
    V : UTF8String;
begin
    I := Pos(At, S);
    if I <= 0 then
        Exit;
    I := I + Length(At);
    V := UTF8String(Format('%08.8d', [N]));
    Move(V[1], S[I], Length(V));
end;
Run Code Online (Sandbox Code Playgroud)

综上所述,将 HTML 格式的数据复制到剪贴板的函数如下所示:

procedure CopyHtmlToClipboard(const HtmlSrc : UTF8String);
var
    Svc      : IFMXExtendedClipboardService;
    Stream   : TStringStream;
    HtmlData : UTF8String;
const
    ClipFormat = 'HTML format';   // This is what Windows expect
                                  // Maybe other platform want something else
begin
    HtmlData := FormatHtmlForClipboard(HtmlSrc);
    Stream   := TStringStream.Create(HtmlData);
    if TPlatformServices.Current.SupportsPlatformService(
                    IFMXExtendedClipboardService, Svc) then begin
        if not Svc.IsCustomFormatRegistered(ClipFormat) then
            Svc.RegisterCustomFormat(ClipFormat);
        Svc.SetCustomFormat(ClipFormat, Stream);
    end;
end;
Run Code Online (Sandbox Code Playgroud)

您应该像这样使用该函数:

procedure TForm1.Button1Click(Sender: TObject);
begin
    CopyHtmlToClipboard(
        '<!DOCTYPE><HTML><BODY><P>' +
        'This is a <b>bold</b> html line' +    //<== Your actual HTML text
        '</P></BODY></HTML>');
end;
Run Code Online (Sandbox Code Playgroud)