Вводная.
Пусть есть множество форматов текстов.
Например:
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 итерацию, при первой кажется проще написать прямой"
Комментариев нет:
Отправить комментарий