Как скопировать текст в формате HTML Как HTML в буфер обмена в Firemonkey

Используя Delphi FireMonkey, мне нужно скопировать строку в формате HTML в буфер обмена в Delphi, чтобы другое приложение, вставившее ее, увидело ее как HTML.

Я уже пытаюсь скопировать простой текст через скрытый компонент Memo, и он работает, но копируется как простой текст. Мне нужна копия в формате 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;

Но проблема в том, что если я вставлю текст, скопированный в буфер обмена, в Microsoft Word, он будет вставлен как:

  • «Это <b>bold</b> html-строка». Я хочу, и это должно быть так:

  • Это жирная строка html

Примечание. Я читал другие обсуждения о том, как это можно сделать в Windows, но мне нужно решение Firemonkey для кросс-платформенного приложения. Любая помощь приветствуется.

Мы не знаем, что вы копируете или куда вставляете. У нас нет подробностей о вашем коде. Пожалуйста, пройдите тур, чтобы узнать, как получить максимальную отдачу от этого сайта, а затем соответствующим образом отредактируйте вопрос.

David Heffernan 11.12.2020 14:05

Спасибо за руководство. Я обновил вопрос соответственно с примером.

HilalSoftware 11.12.2020 14:42

Здесь не будет простого ответа. В Windows вы, вероятно, захотите поместить rtf и html в буфер обмена. Я ожидаю, что это потребует некоторого индивидуального кодирования с вашей стороны.

David Heffernan 11.12.2020 15:21

У меня есть решение для вас. Как только вопрос будет открыт, я опубликую. Это единственная функция CopyHtmlToClipboard для выполнения этой работы.

fpiette 11.12.2020 17:14

@fpiette Спасибо. Да, пожалуйста. Я отчаянно нуждаюсь в этом. Я не закрыл вопрос и не знаю, как открыть его снова.

HilalSoftware 11.12.2020 19:04

@HilalSoftware Прочитайте комментарии ... Ваш вопрос был закрыт по причине «нужны подробности или ясность». Чтобы его снова открыть, вы должны отредактировать его и добавить деталей и ясности (я уже отредактировал и добавил немного ясности). Закрытие и повторное открытие вопросов в StackOverflow осуществляется с помощью голосования. Вы найдете, кто проголосовал за закрытие вашего вопроса, в синей области над вашим вопросом. Теперь вам нужно дождаться повторного открытия вашего вопроса. Я уже проголосовал за это. На StackOverflow вам абсолютно необходимо следовать правилам, иначе ваши вопросы будут отклонены. Посмотрите это: stackoverflow.com/help/how-to-ask

fpiette 11.12.2020 19:13

@fpiette Спасибо за руководство. Я уже редактировал и добавлял детали после того, как он был закрыт. Область блюза не показывает мне, кто закрыл вопрос. Я программист Delphi со времен Delphi 1, и мое эмпирическое правило заключается в том, что если проблема достаточно общая, то какой-нибудь умный разработчик уже знает ее решение; надо поискать и правильно спросить. Спасибо за редактирование. Теперь вопрос кажется достаточно очевидным и ясным. Правила и структура StackOverflow очень хороши, и я жду, когда вопрос будет открыт.

HilalSoftware 11.12.2020 19:32

@fpiette Спасибо. Ждем ваших указаний по этому поводу.

HilalSoftware 12.12.2020 01:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
961
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Копирование данных в буфер обмена кроссплатформенным способом с помощью FireMonkey осуществляется через интерфейс IFMXExtendedClipboardService, который можно получить, вызвав сервис платформы:

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

Этот интерфейс имеет методы для копирования/получения текста/изображений в/из буфера обмена: 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;

Формат должен быть зарегистрирован только один раз, поэтому вызов 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>

Фактическая строка такая же, как и выше, с CRLF в конце каждой строки. Имя формата — «формат HTML».

Вы видите, что предложение This is a <b>bold</b> html line должно быть окружено тегами HTML, чтобы сформировать действительный полный HTML-документ, и ему должен предшествовать заголовок, состоящий из нескольких пар keyword:value. Значения смещаются, если строка. Ключевые слова говорят сами за себя. Строка представляет собой кодировку UTF8, которую можно сократить до ANSI, если вы используете объекты HTML для представления специальных символов.

Я написал функцию для построения всей строки:

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;

При этом функция копирования данных в формате 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;

Вы должны использовать функцию следующим образом:

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;

Красиво и замечательно объяснено. Позвольте мне проверить быстрый проект fmx и вернуться за любыми дополнительными указаниями. Большое спасибо.

HilalSoftware 12.12.2020 09:15

Большое спасибо. Прекрасно работает. Моя проблема решилась моментально. Пометил это решение как принятый ответ. Просто небольшое дополнительное руководство. Если я использую этот код для символов Unicode, они копируются как ????. Я изменил все «AnsiString» на «String», но все равно не работает. Что еще можно сделать здесь, чтобы сделать его совместимым с Unicode? Большое спасибо.

HilalSoftware 12.12.2020 09:39

@HilalSoftware О юникоде: как я уже сказал, HTML должен использовать UTF8. Вы можете использовать UTF8String вместо AnsiString. Вы должны преобразовать свои строки (Unicode UTF16) в UTF8, если они содержат специальные символы. Обратите внимание, что рендеринг HTML в целевом приложении (Word или другом) не обязательно будет принимать какие-либо символы в строке. Вы должны использовать сущности HTML (см. dev.w3.org/html5/html-author/charref).

fpiette 12.12.2020 11:12

Решено. Проблема связана с TStringStream, как описано здесь: stackoverflow.com/questions/2557358/… «TStringStream использует внутри системную кодировку ANSI по умолчанию (1 байт на символ)». Таким образом, в этом отношении изменение create следующим образом устраняет проблему Stream := TStringStream.Create(HtmlData,TEncoding.UTF8);// TEncoding.Unicode); Не удалось решить это как TEncoding.Unicode, но TEncoding.UTF8 выполняет свою работу.

HilalSoftware 12.12.2020 11:18

Короче говоря, чтобы сделать описанные выше подпрограммы совместимыми с Unicode, мы должны: 1. Объявить все "AnsiString" как "String" 2. Создать поток с помощью TEncoding.UTF8 Большое спасибо за помощь. Нужно ли мне изменить ответ, чтобы сделать его совместимым с Unicode?

HilalSoftware 12.12.2020 11:18

@HilalSoftware Вы можете изменить все AnsiString на UTF8String (в любом случае это AnsiString с CodePage 65001). Наиболее важно при вызове функции верхнего уровня CopyHtmlToClipboard передать строку в кодировке UTF8. Это будет работать, но не гарантирует, что символы, отличные от US-ASCII, будут правильно отображаться в целевом приложении. Как я уже сказал, лучше оставаться с AnsiString и использовать HTML Entities. Например, по-французски лето — это «été». Правильный способ представить это в HTML - "&eacute;t&eacute;". Я уверен, что вы легко найдете код Delphi для преобразования строки в AnsiString с объектами HTML.

fpiette 12.12.2020 11:37

@HilalSoftware Я отредактировал свой ответ, заменив все AnsiString на UTF8String. Если вам нужна дополнительная помощь с UTF8String или объектами HTML, задайте новый вопрос (сначала проведите самостоятельное исследование, чтобы обязательно написать «хороший вопрос» и избежать того, чтобы ваш вопрос был «закрыт как дубликат» или «нужны подробности и ясность»). ").

fpiette 12.12.2020 11:52

Спасибо за обновление вашего ответа. Ничего больше не требуется сейчас для проблемы под рукой. Совместимость с UTF8String является бонусом. Будьте благословлены за сотрудничество и помощь.

HilalSoftware 12.12.2020 12:43

Другие вопросы по теме