Вводная.
Пусть есть множество форматов текстов.
Например:
RTF
DOC
DOCX
HTML
TXT
И пусть стоит задача сделать преобразование из одного формата в другой.
Как это сделать?
Можно конечно написать набор преобразований типа:
RTF -> DOC
RTF -> HTML
RTF -> DOCX
HTML -> RTF
HTML -> DOC
ну и так далее...
Только тут вот какая штука - в итоге получаем "Це из Эн по Ка".
Нездорово как-то...
Что делать?
У меня есть ОТВЕТ продиктованный моей МНОГОЛЕТНЕЙ практикой обработки документов.
Надо для начала ввести ОДИН так называемый "канонический формат", который включает в себя всё многообразие "вариантов использования".
Т.е. этот "канонический формат" должен уметь "вмещать в себя" все остальные форматы.
Одним из кандидатов на "каноничность" является XML (точнее XML-подобные структуры), в силу его "расширяемости".
Ну или NSAttributedString.
Подробности чуть позже.
А пока - опишу идею.
Если мы выбрали "канонический формат", то дальше мы делаем вот что:
Мы делаем набор "читателей" и набор "писателей".
Точнее сначала мы "стандартизуем" интерфейс "генератора" типа:
"Мою" реализацию можно посмотреть тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2Interfaces.pas и тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2Prim.pas
Она выглядит так:
И тогда получаем примерно такую картину:
Входной файл -> Читатель -> Писатель (реализующий интерфейс IGenerator) -> Выходной файл.
Что мы имеем?
Читатель читает входной файл из конкретного формата и "преобразует" его к "каноническому формату".
Писатель получает "канонический формат" и преобразует его к конкретному выходному формату.
Т.е. получаем набор читателей:
TRTFReader
TDOCReader
TDOCXReader
THTMLReader
TTXTReader
И набор писателей:
TRTFWriter
TDOCWriter
TDOCXWriter
THTMLWriter
TTXTWriter
Примеры можно посмотреть тут:
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdReader.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdWriter.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdXMLReader.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdXMLWriter.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/dd/ddHTMLReader.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/dd/ddHTMLWriter.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/dd/ddRTFReader.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/dd/ddRTFWriter.pas
ну и так далее...
Таким образом мы получаем уже не "Це из Эн по Ка", а N - Reader'ов и N Writer'ов. Со стандартизованным входом и выходом.
Любой Reader можно прикрутить к любому Writer'у.
Можно даже сделать нуль-преобразование:
TRTFReader -> TRTFWriter
или
THTMLReader -> THTMLWriter
и таком образом протестировать ПОЛНОТУ преобразования "формата самого в себя".
Далее вот как разовьём тему.
Предположим нам не только надо преобразовывать форматы, но и трансформировать их.
Ну например выкидывать какие-то данные или добавлять новые.
Тогда картинка получается следующая:
Входной файл -> Читатель -> Фильтр1 -> Фильтр2 -> Фильтр3 -> Трансформатор1 -> Трансформатор2 -> Трансформатор3 -> Фильтр4 -> Фильтр5 -> Фильтр6 -> Трансформатор4 -> Трансформатор5 -> Трансформатор6 -> Писатель -> Выходной файл
Что Фильтр, что Трансформатор - опять же поддерживают интерфейс IGenerator.
Примеры:
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdHyperlinkEliminator.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdAllDocumentSubsEliminator.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdCommentFilter.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdCommentFilter.pas
Т.е. мы можем не только ПРЕОБРАЗОВЫВАТЬ форматы, но и ПРЕОБРАЗОВЫВАТЬ данные.
Простейший пример:
Входной файл -> TRTFReader -> Фильтр выкидывающий пустые параграфы -> TRTFWriter -> Выходной файл
Или:
Входной файл -> TRTFReader -> Фильтр выкидывающий гиперссылки -> TRTFWriter -> Выходной файл
Или:
Входной файл -> TRTFReader -> Фильтр выкидывающий гиперссылки ->Фильтр выкидывающий пустые параграфы -> TRTFWriter -> Выходной файл
Мысль понятна?
Ещё на ту же тему:
http://18delphi.blogspot.ru/2013/04/blog-post_10.html
http://18delphi.blogspot.ru/2013/04/blog-post_8517.html
http://18delphi.blogspot.ru/2013/11/coretext.html
http://18delphi.blogspot.ru/2013/10/coretext_30.html
http://18delphi.blogspot.ru/2013/10/offtopic-evd-coretext.html
http://18delphi.blogspot.ru/2013/10/coretext-glyphrunnerdelegate.html
http://18delphi.blogspot.ru/2013/10/coretext-nsattributedstring.html
Аналогичным образом строится работа с редактором:
Чтение:
Входной файл -> Читатель -> Буфер документ (как разновидность генератора) -> Редактор
Запись:
Буфер документа (принадлежащий редактору) -> Писатель -> Выходной файл
Пример буфера(ов) тут:
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2DocumentGenerator.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2DocumentBuffer.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2InPlaceGenerator.pas
Мысль понятна?
Тема вообще интересна?
Продолжать?
P.S. Ну и на закуску - ForkGenerator - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2ForkGenerator.pas
Ну я думаю, что понятно что он делает:
Мысль понятна?
P.P.S. Написали тут:
"Ну просто идея уж больно "естественная"
правда возникает она после 2-3 перекодировщика
с поправкой на 3-4 итерацию, при первой кажется проще написать прямой"
Пусть есть множество форматов текстов.
Например:
RTF
DOC
DOCX
HTML
TXT
И пусть стоит задача сделать преобразование из одного формата в другой.
Как это сделать?
Можно конечно написать набор преобразований типа:
RTF -> DOC
RTF -> HTML
RTF -> DOCX
HTML -> RTF
HTML -> DOC
ну и так далее...
Только тут вот какая штука - в итоге получаем "Це из Эн по Ка".
Нездорово как-то...
Что делать?
У меня есть ОТВЕТ продиктованный моей МНОГОЛЕТНЕЙ практикой обработки документов.
Надо для начала ввести ОДИН так называемый "канонический формат", который включает в себя всё многообразие "вариантов использования".
Т.е. этот "канонический формат" должен уметь "вмещать в себя" все остальные форматы.
Одним из кандидатов на "каноничность" является XML (точнее XML-подобные структуры), в силу его "расширяемости".
Ну или NSAttributedString.
Подробности чуть позже.
А пока - опишу идею.
Если мы выбрали "канонический формат", то дальше мы делаем вот что:
Мы делаем набор "читателей" и набор "писателей".
Точнее сначала мы "стандартизуем" интерфейс "генератора" типа:
type IGenerator = interface procedure AddAttribute(aName: String; aValue: String); procedure StartStructure(aName: String); procedure StartArray(aName: String); procedure Finish; end;//IGenerator
"Мою" реализацию можно посмотреть тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2Interfaces.pas и тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2Prim.pas
Она выглядит так:
Ik2TagGenerator = interface(IUnknown)
{* Генератор тегов }
['{694DAEA5-80F3-4E12-9CCF-2B9950479734}']
procedure pm_SetCharsInLine(aValue: Integer);
function pm_GetCurrentStartLevel: Integer;
function pm_GetNextGenerator: Ik2TagGenerator;
procedure pm_SetNextGenerator(const aValue: Ik2TagGenerator);
function pm_GetContext: Ik2Op;
function Get_CurrentVersion: Integer;
procedure Set_CurrentVersion(aValue: Integer);
procedure AddStringAtom(TagID: Integer;
const Value: AnsiString;
aCodePage: Integer = CP_ANSI); overload;
{* добавить строковый атом. }
procedure AddPCharLenAtom(TagID: Integer;
const Value: Tl3WString);
{* добавить строковый атом. }
procedure AddObjectAtom(TagID: Integer;
Value: TObject;
Shared: Boolean = true);
procedure AddStreamAtom(TagID: Integer;
aStream: TStream);
{* добавить атом из потока. }
procedure AddTransparentAtom(TagID: Integer);
{* добавить "прозрачный" атом. }
procedure AddIntegerAtom(TagID: Integer;
Value: Integer);
{* добавить целочисленный атом. }
procedure AddBoolAtom(TagID: Integer;
Value: Boolean);
procedure AddAtom(AtomIndex: Integer;
TK: Tk2TypeKind;
const Value);
{* добавить атом. }
procedure AddStringAtom(TagID: Integer;
Value: Tl3PrimString); overload;
procedure AddAtomEx(AtomIndex: Integer;
const Value: Tk2Variant);
procedure Start;
{* начать генерацию. }
procedure StartChild(TypeID: Integer);
{* начать дочерний объект тега. }
procedure StartDefaultChild;
{* начать дочерний объект тега с типом по-умолчанию. }
procedure StartTag(TagID: Integer);
{* начать вложеный тег. }
procedure Finish(NeedUndo: Boolean = false);
{* закрыть скобку этапа генерации. }
procedure Rollback(CheckBrackets: Boolean = false);
{* откатить все открытые "скобки". }
function Pixel2Char(Pixel: Integer): Integer;
function Char2Pixel(Ch: Integer): Integer;
procedure AddStringAtomClone(TagID: Integer;
Value: Tl3CustomString);
procedure AddInt64Atom(aTagID: Integer;
aValue: Int64);
{* Добавляет 64-битный атом }
property CharsInLine: Integer
write pm_SetCharsInLine;
property CurrentStartLevel: Integer
read pm_GetCurrentStartLevel;
property NextGenerator: Ik2TagGenerator
read pm_GetNextGenerator
write pm_SetNextGenerator;
{* следующий генератор в цепочке. }
property Context: Ik2Op
read pm_GetContext;
{* Контекст генерации }
property CurrentVersion: Integer
read Get_CurrentVersion
write Set_CurrentVersion;
{* Текущая версия формата }
end;//Ik2TagGenerator
И тогда получаем примерно такую картину:
Входной файл -> Читатель -> Писатель (реализующий интерфейс IGenerator) -> Выходной файл.
Что мы имеем?
Читатель читает входной файл из конкретного формата и "преобразует" его к "каноническому формату".
Писатель получает "канонический формат" и преобразует его к конкретному выходному формату.
Т.е. получаем набор читателей:
TRTFReader
TDOCReader
TDOCXReader
THTMLReader
TTXTReader
И набор писателей:
TRTFWriter
TDOCWriter
TDOCXWriter
THTMLWriter
TTXTWriter
Примеры можно посмотреть тут:
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdReader.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdWriter.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdXMLReader.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdXMLWriter.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/dd/ddHTMLReader.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/dd/ddHTMLWriter.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/dd/ddRTFReader.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/dd/ddRTFWriter.pas
ну и так далее...
Таким образом мы получаем уже не "Це из Эн по Ка", а N - Reader'ов и N Writer'ов. Со стандартизованным входом и выходом.
Любой Reader можно прикрутить к любому Writer'у.
Можно даже сделать нуль-преобразование:
TRTFReader -> TRTFWriter
или
THTMLReader -> THTMLWriter
и таком образом протестировать ПОЛНОТУ преобразования "формата самого в себя".
Далее вот как разовьём тему.
Предположим нам не только надо преобразовывать форматы, но и трансформировать их.
Ну например выкидывать какие-то данные или добавлять новые.
Тогда картинка получается следующая:
Входной файл -> Читатель -> Фильтр1 -> Фильтр2 -> Фильтр3 -> Трансформатор1 -> Трансформатор2 -> Трансформатор3 -> Фильтр4 -> Фильтр5 -> Фильтр6 -> Трансформатор4 -> Трансформатор5 -> Трансформатор6 -> Писатель -> Выходной файл
Что Фильтр, что Трансформатор - опять же поддерживают интерфейс IGenerator.
Примеры:
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdHyperlinkEliminator.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdAllDocumentSubsEliminator.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdCommentFilter.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/EVD/evdCommentFilter.pas
Т.е. мы можем не только ПРЕОБРАЗОВЫВАТЬ форматы, но и ПРЕОБРАЗОВЫВАТЬ данные.
Простейший пример:
Входной файл -> TRTFReader -> Фильтр выкидывающий пустые параграфы -> TRTFWriter -> Выходной файл
Или:
Входной файл -> TRTFReader -> Фильтр выкидывающий гиперссылки -> TRTFWriter -> Выходной файл
Или:
Входной файл -> TRTFReader -> Фильтр выкидывающий гиперссылки ->Фильтр выкидывающий пустые параграфы -> TRTFWriter -> Выходной файл
Мысль понятна?
Ещё на ту же тему:
http://18delphi.blogspot.ru/2013/04/blog-post_10.html
http://18delphi.blogspot.ru/2013/04/blog-post_8517.html
http://18delphi.blogspot.ru/2013/11/coretext.html
http://18delphi.blogspot.ru/2013/10/coretext_30.html
http://18delphi.blogspot.ru/2013/10/offtopic-evd-coretext.html
http://18delphi.blogspot.ru/2013/10/coretext-glyphrunnerdelegate.html
http://18delphi.blogspot.ru/2013/10/coretext-nsattributedstring.html
Аналогичным образом строится работа с редактором:
Чтение:
Входной файл -> Читатель -> Буфер документ (как разновидность генератора) -> Редактор
Запись:
Буфер документа (принадлежащий редактору) -> Писатель -> Выходной файл
Пример буфера(ов) тут:
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2DocumentGenerator.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2DocumentBuffer.pas
https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2InPlaceGenerator.pas
Мысль понятна?
Тема вообще интересна?
Продолжать?
P.S. Ну и на закуску - ForkGenerator - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/RealWork/K2/k2ForkGenerator.pas
Ну я думаю, что понятно что он делает:
Писатель1 -> Выходной файл1
/
Входной файл -> Читатель -> ForkGenerator
\
Писатель2 -> Выходной файл2
Мысль понятна?
P.P.S. Написали тут:
"Ну просто идея уж больно "естественная"
правда возникает она после 2-3 перекодировщика
с поправкой на 3-4 итерацию, при первой кажется проще написать прямой"
Комментариев нет:
Отправить комментарий