Неофициальное введение в DOCX

Опубликовано: 2022-03-11

Microsoft Office использует около миллиарда человек, поэтому формат DOCX является де-факто самым популярным стандартом для обмена файлами документов между офисами. Его ближайший конкурент — формат ODT — поддерживается только Open/LibreOffice и некоторыми продуктами с открытым исходным кодом, что делает его далеким от стандарта. Формат PDF не является конкурентом, потому что PDF-файлы нельзя редактировать и они не содержат полной структуры документа, поэтому они могут вносить только ограниченные локальные изменения, такие как водяные знаки, подписи и т.п. Вот почему большинство деловых документов создаются в формате DOCX; нет хорошей альтернативы, чтобы заменить его.

Хотя DOCX является сложным форматом, вы можете проанализировать его вручную для более простых задач, таких как индексация, преобразование в TXT и внесение других небольших изменений. Я хотел бы дать вам достаточно информации о внутреннем устройстве DOCX, чтобы вам не пришлось ссылаться на спецификации ECMA, огромное руководство на 5000 страниц.

Лучший способ понять формат — создать простой документ из одного слова с помощью MSWord и посмотреть, как редактирование документа изменяет базовый XML. Вы столкнетесь с некоторыми случаями, когда DOCX неправильно форматируется в MS Word, и вы не знаете, почему, или столкнетесь со случаями, когда неясно, как создать желаемое форматирование. В этом поможет видение и понимание того, что именно происходит в XML.

Около года я работал над редактором DOCX для совместной работы CollabOffice и хочу поделиться некоторыми знаниями с сообществом разработчиков. В этой статье я объясню файловую структуру DOCX, обобщив информацию, разбросанную по Интернету. Эта статья является промежуточным звеном между огромной и сложной спецификацией ECMA и простыми онлайн-руководствами, доступными в настоящее время. Вы можете найти файлы, сопровождающие эту статью, в проекте toptal-docx в моей учетной записи github.

Простой файл DOCX

Файл DOCX представляет собой ZIP-архив XML-файлов. Если вы создадите новый пустой документ Microsoft Word, напишите внутри одно слово «Тест» и разархивируете его содержимое, вы увидите следующую файловую структуру:

Наша новая тестовая структура DOCX.

Несмотря на то, что мы создали простой документ, в процессе сохранения в Microsoft Word были созданы темы по умолчанию, свойства документа, таблицы шрифтов и т. д. в формате XML.

Все файлы внутри DOCX являются файлами XML, даже с расширением «.rels».
Твитнуть

Для начала давайте удалим неиспользуемый материал и сосредоточимся на document.xml , который содержит основные текстовые элементы. Когда вы удаляете файл, убедитесь, что вы удалили все ссылки на него из других файлов xml. Вот пример сравнения кода о том, как я очистил зависимости от app.xml и core.xml. Если у вас есть какие-либо неразрешенные/отсутствующие ссылки, MSWord будет считать файл поврежденным.

Вот структура нашего упрощенного минимального документа DOCX (а вот и проект на github):

Наша упрощенная структура DOCX.

Давайте разберем это по файлам отсюда, сверху:

_rels/.rels

Это определяет ссылку, которая сообщает MS Word, где искать содержимое документа. В данном случае это ссылка на word/document.xml :

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/> </Relationships>

_rels/document.xml.rels

Этот файл определяет ссылки на ресурсы, такие как изображения, встроенные в содержимое документа. В нашем простом документе нет встроенных ресурсов, поэтому тег отношения пуст:

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> </Relationships>

[Типы_содержимого].xml

[Content_Types].xml содержит информацию о типах мультимедиа внутри документа. Поскольку у нас есть только текстовый контент, это довольно просто:

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> <Default Extension="xml" ContentType="application/xml"/> <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/> </Types>

документ.xml

Наконец, вот основной XML с текстовым содержимым документа. Я удалил некоторые объявления пространств имен для ясности, но вы можете найти полную версию файла в проекте github. В этом файле вы обнаружите, что некоторые ссылки на пространство имен в документе не используются, но вам не следует их удалять, потому что они нужны MS Word.

Вот наш упрощенный пример:

 <w:document> <w:body> <w:pw:rsidR="005F670F" w:rsidRDefault="005F79F5"> <w:r><w:t>Test</w:t></w:r> </w:p> <w:sectPr w:rsidR="005F670F"> <w:pgSz w:w="12240" w:h="15840"/> <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/> <w:cols w:space="720"/> <w:docGrid w:linePitch="360"/> </w:sectPr> </w:body> </w:document>

Главный узел <w:document> представляет сам документ, <w:body> содержит абзацы, а внутри <w:body> вложены размеры страницы, определенные <w:sectPr> .

<w:rsidR> — это атрибут, который вы можете игнорировать; он используется внутренними компонентами MS Word.

Давайте взглянем на более сложный документ с тремя абзацами. Я выделил XML теми же цветами на скриншоте из Microsoft Word, чтобы вы могли видеть взаимосвязь:

Пример сложного абзаца со стилем.

<w:pw:rsidR="0081206C" w:rsidRDefault="00E10CAE"> <w:r> <w:t xml:space="preserve">Это первый абзац нашего примера. По умолчанию он выровнен по левому краю, и теперь я хотел бы представить</w:t> </w:r> <w:r> <w:rPr> <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:цвет w:val="000000"/> </w:rPr> <w:t>немного жирного шрифта</w:t> </ш:р> <w:r> <w:rPr> <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:b/> <w:color w:val="000000"/> </w:rPr> <w:t xml:space="preserve"> текст</w:t> </ш:р> <w:r> <w:rPr> <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:color w:val="000000"/> </w:rPr> <w:t xml:space="preserve">, </w:t> </ш:р> <w:proofErr w:type="gramStart"/> <w:r> <w:t xml:space="preserve">а также изменить</w:t> </w:r> <w:rw:rsidRPr="00E10CAE"> <w:rPr><w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> </w:rPr> <w:t>стиль шрифта</w:t> </w:r> <ж:р> <w:rPr> <w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> </w:rPr> <w:t xml:space="preserve"> </w:t> </ш:р> <w:r> <w:t>на "Воздействие".</w:t></w:r> </ш:р> <w:pw:rsidR="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Это новый абзац.</w:t> </w:r></w:p > <w:pw:rsidR="00E10CAE" w:rsidRPr="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Это еще один абзац, немного длиннее.</w:t> </w:r> </ш:р>

Структура абзаца

Простой документ состоит из абзацев, абзац состоит из строк (серии текста с одинаковым шрифтом, цветом и т. д.), а строки состоят из символов (таких как <w:t> ). Теги <w:t> могут содержать несколько символов внутри, и их может быть несколько в одном и том же прогоне.

Опять же, мы можем игнорировать <w:rsidR> .

Свойства текста

Основными свойствами текста являются шрифт, размер, цвет, стиль и т. д. Существует около 40 тегов, определяющих внешний вид текста. Как вы можете видеть в нашем примере с тремя абзацами, каждый прогон имеет свои собственные свойства внутри <w:rPr> , определяющие <w:color> , <w:rFonts> и жирность <w:b> .

Важно отметить, что свойства делают различие между двумя группами символов, обычным и сложным письмом (например, арабским), и что свойства имеют разные теги в зависимости от того, на какой тип символов они влияют.

Большинство обычных тегов свойств сценария имеют соответствующий тег сложного сценария с добавленной буквой «C», указывающей, что свойство предназначено для сложных сценариев. Например: <w:i> (курсив) становится <w:iCs> , а полужирный тег для обычного письма <w:b> становится <w:bCs> для сложного письма.

Стили

В Microsoft Word есть целая панель инструментов, посвященная стилям: обычный, без интервала, заголовок 1, заголовок 2, заголовок и так далее. Эти стили хранятся в /word/styles.xml (примечание: на первом шаге в нашем простом примере мы удалили этот XML из DOCX. Чтобы увидеть это, создайте новый DOCX).

Как только вы определили текст как стиль, вы найдете ссылку на этот стиль внутри тега свойств абзаца <w:pPr> . Вот пример, где я определил свой текст со стилем Заголовок 1:

 <w:p> <w:pPr> <w:pStyle w:val="Heading1"/> </w:pPr> <w:r> <w:t>My heading 1</w:t> </w:r> </w:p>

а вот и сам стиль из styles.xml :

 <w:style w:type="paragraph" w:style> <w:name w:val="heading 1"/> <w:basedOn w:val="Normal"/> <w:next w:val="Normal"/> <w:link w:val="Heading1Char"/> <w:uiPriority w:val="9"/> <w:qFormat/> <w:rsid w:val="002F7F18"/> <w:pPr> <w:keepNext/> <w:keepLines/> <w:spacing w:before="480" w:after="0"/> <w:outlineLvl w:val="0"/> </w:pPr> <w:rPr> <w:rFonts w:asciiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" w:hAnsiTheme="majorHAnsi" w:cstheme="majorBidi"/> <w:b/> <w:bCs/> <w:color w:val="365F91" w:themeColor="accent1" w:themeShade="BF"/> <w:sz w:val="28"/> <w:szCs w:val="28"/> </w:rPr> </w:style>

<w:style/w:rPr/w:b> указывает, что шрифт выделен полужирным, а <w:style/w:rPr/w:color> указывает цвет шрифта. <w:basedOn> указывает MSWord использовать «Обычный» стиль для всех отсутствующих свойств.

Наследование имущества

Свойства текста наследуются. Прогон имеет свои собственные свойства ( w:p/w:r/w:rPr/* ), но он также наследует свойства абзаца ( w:r/w:pPr/* ), и оба могут ссылаться на свойства стиля из /word/styles.xml .

 <w:r> <w:rPr> <w:rStyle w:val="DefaultParagraphFont"/> <w:sz w:val="16"/> </w:rPr> <w:tab/> </w:r>

Абзацы и прогоны начинаются со свойствами по умолчанию: w:styles/w:docDefaults/w:rPrDefault/* и w:styles/w:docDefaults/w:pPrDefault/* . Чтобы получить конечный результат свойств персонажа, вы должны:

  1. Использовать свойства запуска/абзаца по умолчанию
  2. Добавить свойства стиля запуска/абзаца
  3. Добавить локальные свойства запуска/абзаца
  4. Добавить свойства прогона результата поверх свойств абзаца

Когда я говорю «присоединить» B к A, я имею в виду перебор всех свойств B и переопределение всех свойств A, оставляя все непересекающиеся свойства как есть.

Еще одно место, где могут быть расположены свойства по умолчанию, — это <w:style> с w:type="paragraph" и w:default="1" . Обратите внимание, что сами символы внутри прогона никогда не имеют стиля по умолчанию, поэтому <w:style w:type="character" w:default="1"> на самом деле не влияет на какой-либо текст.

Твитнуть

Символы в прогоне могут наследовать от своего абзаца, и оба они могут наследовать от styles.xml.

1554402290400-dbb29eef3ba6035df7ad726dfc99b2af.png)

Символы в прогоне могут наследовать от своего абзаца, и оба они могут наследовать от styles.xml.

Переключить свойства

Некоторые из свойств являются «переключаемыми» свойствами, например <w:b> (жирный шрифт) или <w:i> (курсив); эти атрибуты ведут себя как оператор XOR.

Это означает, что если родительский стиль выделен полужирным шрифтом, а дочерний стиль выделен полужирным шрифтом, результатом будет обычный текст, не выделенный полужирным шрифтом.

Вы должны провести много тестов и реинжиниринга, чтобы правильно обрабатывать атрибуты переключения. Взгляните на параграф 17.7.3 спецификации Open XML ECMA-376, чтобы получить формальные подробные правила для переключаемых свойств.

Свойства переключения являются наиболее сложными для корректной обработки компоновщиком.
Твитнуть

Шрифты

Шрифты подчиняются тем же общим правилам, что и другие текстовые атрибуты, но значения по умолчанию свойств шрифта указываются в отдельном файле темы, на который ссылаются в word/_rels/document.xml.rels следующим образом:

 <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>

Основываясь на приведенной выше ссылке, имя шрифта по умолчанию будет найдено в word/theme/themes1.xml внутри <a:theme> , тега a:themeElements/a:fontScheme/a:majorFont или a:minorFont .

Размер шрифта по умолчанию — 10, если тег w:docDefaults/w:rPrDefault отсутствует, тогда размер шрифта 11.

Выравнивание текста

Выравнивание текста задается <w:jc> с четырьмя доступными режимами w:val : "left" , "center" , "right" и "both" .

"left" — режим по умолчанию; текст начинается слева от прямоугольника абзаца (обычно на ширину страницы). (Этот абзац выровнен по левому краю, что является стандартным.)

Режим "center" , как и ожидалось, центрирует все символы внутри ширины страницы. (Опять же, этот абзац иллюстрирует выравнивание по центру.)

В "right" режиме текст абзаца выравнивается по правому краю. (Обратите внимание, как этот текст выровнен по правому краю.)

Режим "both" добавляет дополнительный интервал между словами, так что строки становятся шире и занимают всю ширину абзаца, за исключением последней строки, которая выравнивается по левому краю. (Этот абзац является демонстрацией этого.)

Картинки

DOCX поддерживает два типа изображений: встроенные и плавающие.

Встроенные изображения появляются внутри абзаца вместе с другими символами, используется <w:drawing> вместо <w:t> (текст). Вы можете найти идентификатор изображения с помощью следующего синтаксиса xpath:

w:drawing/wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip/@r:embed

Идентификатор изображения используется для поиска имени файла в файле word/_rels/document.xml.rels и должен указывать на файл gif/jpeg внутри подпапки word/media. (См. файл word/_rels/document.xml.rels проекта github, где вы можете увидеть идентификатор изображения.)

Плавающие изображения размещаются относительно абзацев, а текст обтекает их. (Вот пример документа проекта github с плавающим изображением.)

Плавающие изображения используют <wp:anchor> вместо <w:drawing> , поэтому, если вы удаляете какой-либо текст внутри <w:p> , будьте осторожны с якорями, если вы не хотите, чтобы изображения были удалены.

Встроенный против плавающего.

Параметры изображения MS Word относятся к выравниванию изображения как к «режиму переноса текста».

Столы

Теги XML для таблиц аналогичны разметке таблиц HTML. то же самое, что и <table>, соответствует <tr> и т. д.

<w:tbl> , сама таблица, имеет табличные свойства <w:tblPr> , и каждое свойство столбца представлено <w:gridCol> внутри <w:tblGrid> . Строки следуют одна за другой как теги <w:tr> , и каждая строка должна иметь такое же количество столбцов, как указано в <w:tblGrid> :

 <w:tbl> <w:tblPr> <w:tblW w:w="5000" w:type="pct" /> </w:tblPr> <w:tblGrid><w:gridCol/><w:gridCol/></w:tblGrid> <w:tr> <w:tc><w:p><w:r><w:t>left</w:t></w:r></w:p></w:tc> <w:tc><w:p><w:r><w:t>right</w:t></w:r></w:p></w:tc> </w:tr> </w:tbl>

Ширина столбцов таблицы может быть указана в <w:tblW> , но если вы не укажете ее, MS Word будет использовать свои внутренние алгоритмы, чтобы найти оптимальную ширину столбцов для наименьшего эффективного размера таблицы.

Единицы

Многие атрибуты XML внутри DOCX определяют размеры или расстояния. Хотя внутри XML они являются целыми числами, все они имеют разные единицы измерения, поэтому необходимо некоторое преобразование. Тема сложная, поэтому я бы порекомендовал эту статью Ларса Корнелиуссена о единицах измерения в файлах DOCX. Таблица, которую он представляет, полезна, хотя и с небольшой опечаткой: дюймы должны быть pt/72, а не pt*72.

Вот шпаргалка:

ОБЩИЕ ПРЕОБРАЗОВАНИЯ ЕДИНИЦ DOCX XML
20-й балл Точки
дкса/20
Дюймы
пт/72
Сантиметры
дюйм*2,54
Половинный размер шрифта
пт/144
ЭМУ
в*914400
Пример 11906 595,3 8,27… 21.00086… 4135 7562088
Теги, использующие это pgSz/pgMar/w:расстояние ш: сз wp: экстент, а: экстент

Советы по реализации лейаутера

Если вы хотите преобразовать файл DOCX (например, в PDF), нарисовать его на холсте или подсчитать количество страниц, вам придется реализовать компоновщик. Компоновщик — это алгоритм вычисления позиций символов в файле DOCX.

Это сложная задача, если вам нужен рендеринг со 100-процентной точностью. Количество времени, необходимое для реализации хорошего компоновщика, измеряется в человеко-годах, но если вам нужен только простой, ограниченный, его можно сделать относительно быстро.

Компоновщик заполняет родительский прямоугольник, который обычно представляет собой прямоугольник страницы. Он добавляет слова из прогона одно за другим. Когда текущая строка переполняется, она начинает новую. Если абзац слишком высок для родительского прямоугольника, он переносится на следующую страницу.

Вот несколько важных моментов, о которых следует помнить, если вы решите внедрить компоновщик:

  • Компоновщик должен позаботиться о выравнивании текста и наведении текста на изображения.
  • Он должен быть способен обрабатывать вложенные объекты, такие как вложенные таблицы.
  • Если вы хотите обеспечить полную поддержку таких изображений, вам придется реализовать компоновщик как минимум с двумя проходами, первый шаг собирает позиции плавающих изображений, а второй заполняет пустое пространство текстовыми символами.
  • Помните об отступах и пробелах. У каждого абзаца есть интервалы до и после, и эти числа задаются тегом w:spacing . Вертикальный интервал определяется тегами w:after и w:before . Обратите внимание, что межстрочный интервал определяется w:line , но это не размер строки, как можно было бы ожидать. Чтобы получить размер строки, возьмите текущую высоту шрифта, умножьте на w:line и разделите на 12.
  • Файлы DOCX не содержат информации о нумерации страниц. Вы не найдете количество страниц в документе, если не подсчитаете, сколько места вам нужно для каждой строки, чтобы определить количество страниц. Если вам нужно найти точные координаты каждого символа на странице, обязательно учитывайте все пробелы, отступы и размеры.
  • Если вы реализуете полнофункциональную компоновку DOCX, которая обрабатывает таблицы, обратите внимание на особые случаи, когда таблицы занимают несколько страниц. Ячейка, вызывающая переполнение страницы, также влияет на другие ячейки.
  • Создание оптимального алгоритма для вычисления ширины столбцов таблицы является сложной математической задачей, и текстовые процессоры и компоновщики обычно используют некоторые неоптимальные реализации. Предлагаю в качестве первого приближения использовать алгоритм из HTML-табличной документации W3C. Я не нашел описания алгоритма, используемого MS Word, и Microsoft со временем доработала алгоритм, поэтому разные версии Word могут немного по-разному располагать таблицы.

Если что-то непонятно: реконструируйте XML!

Когда не очевидно, как работает тот или иной XML-тег внутри MS Word, есть два основных подхода к выяснению этого:

  • Шаг за шагом создавайте нужный контент. Начните с простого файла docx. Сохраняйте каждый шаг в отдельный файл, например, 1.docx , 2.docx . Разархивируйте каждый из них и используйте инструмент визуального сравнения для сравнения папок, чтобы увидеть, какие теги появляются после ваших изменений. (Чтобы получить коммерческую версию, попробуйте Araxis Merge или бесплатную версию WinMerge.)

  • Если вы создаете файл DOCX, который не нравится MS Word, работайте в обратном порядке. Шаг за шагом упрощайте XML. В какой-то момент вы узнаете, какое изменение MS Word считает неправильным.

DOCX довольно сложен, не так ли?

Это сложно, а лицензия Microsoft запрещает использовать MS Word на стороне сервера для обработки DOCX — это довольно стандартно для коммерческих продуктов. Microsoft, однако, предоставила файл XSLT для обработки большинства тегов DOCX, но он не даст вам 100-процентной или даже 99-процентной точности. Такие процессы, как наложение текста на изображения, не поддерживаются, но вы сможете поддерживать большинство документов. (Если вам не нужна сложность, рассмотрите возможность использования Markdown в качестве альтернативы.)

Если у вас есть достаточный бюджет (бесплатного механизма рендеринга DOCX не существует), вы можете использовать коммерческие продукты, такие как Aspose или docx4j. Самым популярным бесплатным решением является LibreOffice для преобразования между DOCX и другими форматами, включая PDF. К сожалению, LibreOffice содержит много мелких ошибок во время преобразования, и, поскольку это сложный продукт C++ с открытым исходным кодом, проблемы с точностью устраняются медленно и сложно.

В качестве альтернативы, если вы находите макет DOCX слишком сложным для самостоятельной реализации, вы также можете преобразовать его в HTML и использовать браузер для его отображения. Вы также можете рассмотреть вопрос об одном из внештатных XML-разработчиков Toptal.

Ресурсы DOCX для дальнейшего чтения

  • Спецификация ECMA DOCX
  • Библиотека OpenXML для работы с DOCX из C#. Он не содержит информации о макете или коде рендеринга, но предлагает иерархию классов, соответствующую каждому возможному узлу XML в DOCX.
  • Вы всегда можете искать или спрашивать в stackoverflow с такими ключевыми словами, как docx4j, OpenXML и docx; в сообществе есть знающие люди.