Рейтинг@Mail.ru
[Войти] [Зарегистрироваться]

Наши друзья и партнеры

UnixForum
купить дешевый 
компьютер родом из Dhgate.com




Lines Club

Ищем достойных соперников.

Библиотека сайта rus-linux.net

Разработка дополнения для LibreOffice


Автор: A. Панин
Дата публикации: 17 апреля 2015 г.

При разговоре об офисных пакетах для Linux чаще всего упоминают такие пакеты, как LibreOffice и OpenOffice. Более того, в большинстве дистрибутивов пакет LibreOffice предустановлен по умолчанию. В данной статье будет описываться процесс разработки простого дополнения для данного пакета, позволяющего преобразовывать открытый текстовый документ в документ формата HTML, оформленный в соответствии с требованиями сайта "Виртуальная энциклопедия "Linux по-русски". Может возникнуть вопрос: "Почему просто не выбрать формат "Документ HTML" в диалоге сохранения файла?" Ответ достаточно прост: во-первых, при сохранении документа будет использована кодировка UTF-8, а не KOI8-R; во-вторых, в этом случае не удастся использовать фрагменты шаблона оформления материалов для публикации на сайте.

1. Установка необходимых программных компонентов

Как и у любого крупного проекта, у проекта LibreOffice имеется стандартный набор программных компонентов для разработчиков. Сами дополнения по сути являются архивами формата zip, поэтому также понадобится соответствующий архиватор. Ввиду того, что для разработки дополнения будет использоваться язык программирования Python, для исполнения основного сценария дополнения понадобится интерпретатор Python и биндинги для интерфейса UNO офисного пакета (PyUNO). Ну и разумеется, для разработки дополнения понадобится сам пакет LibreOffice, который будет установлен в любом случае благодаря зависимостям между пакетами программного обеспечения дистрибутива.

В дистрибутивах Fedora/RHEL/CentOS необходимо выполнить следующую команду:

yum install libreoffice-pyuno libreoffice-sdk zip

В случае использования Fedora 22 и более поздних версий дистрибутива для установки пакетов программного обеспечения следует использовать утилиту dnf вместо yum.

dnf install libreoffice-pyuno libreoffice-sdk zip

В дистрибутивах Debian/Ubuntu соответствующие пакеты устанавливаются с помощью следующей команды:

apt-get install python-uno libreoffice_dev zip

В ходе разработки для тестирования дополнения использовалась версия 4.3.6.2 офисного пакета LibreOffice. Работоспособность дополнения при условии использования более ранних версий данного офисного пакета по понятным причинам не может быть гарантирована. Условием последующей корректной эксплуатации дополнения, разработанного с использованием языка программирования Python, является установка в систему интерпретатора Python и биндингов для интерфейса UNO офисного пакета (PyUNO).

2. Подготовка файлов дополнения

Разрабатываемое дополнение будет достаточно простым, поэтому мы не будем создавать большое количество файлов. Для начала создадим в рабочей директории директорию ruslinux, в которой будут храниться все файлы дополнения. Впоследствии эти файлы будут упаковываться в архив формата zip, который будет сохраняться в рабочей директории. В данной директории нужно создать еще одну директорию с именем META-INF. Перейдем в эту директорию и создадим в ней файл с именем manifest.xml. В нашем случае содержимое этого файла будет следующим:

<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest>
<manifest:file-entry manifest:full-path="ruslinux.py" manifest:media-type="application/vnd.sun.star.uno-component;type=Python"/>
<manifest:file-entry manifest:full-path="Addons.xcu" manifest:media-type="application/vnd.sun.star.configuration-data"/>
</manifest:manifest>

Очевидно, что в данном файле описываются все файлы дополнения, причем файл с именем ruslinux.py будет являться основным сценарием дополнения на языке программирования Python, а файл Addons.xcu - файлом описания функций дополнения. Все файлы дополнения будут расположены в директории уровнем выше, поэтому перейдем в нее. Файл Addons.xcu в нашем случае будет иметь следующее содержимое:

<?xml version="1.0" encoding="UTF-8"?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry"
            xmlns:xs="http://www.w3.org/2001/XMLSchema"
            oor:name="Addons" oor:package="org.openoffice.Office">
 <node oor:name="AddonUI">
   <node oor:name="AddonMenu">
     <node oor:name="org.openoffice.comp.pyuno.exp.RusLinux" oor:op="replace">
       <prop oor:name="URL" oor:type="xs:string">
         <value>service:org.openoffice.comp.pyuno.exp.RusLinux?export</value>
       </prop>
       <prop oor:name="Title" oor:type="xs:string">
         <value xml:lang="en-US">Export to HTML for rus-linux.net publication</value>
       </prop>
       <prop oor:name="Title" oor:type="xs:string">
         <value xml:lang="ru-RU">Экспорт в формат HTML для публикации на сайте rus-linux.net</value>
       </prop>
     </node>
   </node>
 </node>
</oor:component-data>

При внимательном рассмотрении приведенного выше содержимого файла можно обнаружить, что дополнение работает c объектами LibreOffice посредством прослойки pyuno и носит имя RusLinux, пункт для его активации будет добавлен в меню дополнений LibreOffice (Сервис-Дополнения), причем текст пункта меню переведен на русский язык, а в результате активации этого пункта меню будет генерироваться событие "export". В нашем случае название события не имеет решающего значения.

Пункт активации дополнения в меню дополнений
Рисунок 1 - Пункт активации дополнения в меню дополнений

Теперь создадим файл сценария дополнения, который будет всего лишь выводить строку "Hello world" и, в соответствии с содержимым файла manifest.xml, иметь имя ruslinux.py. Мы сможем увидеть упомянутую строку среди строк отладки дополнения в терминале на этапе тестирования. На данном этапе файл сценария будет содержать следующий код:

Как можно догадаться, в данном виде сценарий предназначен лишь для регистрации класса службы UNO с заданным именем RusLinuxJob, причем в конструкторе класса происходит сохранение контекста компонента, а функция trigger предназначена для обработки событий. В нашем случае при активации пункта в меню дополнений LibreOffice будет сгенерировано событие export, в результате чего будет осуществлен вызов функции trigger с передачей строки export посредством ее аргумента event. Вы наверняка обратили внимание на то, что для вывода строки "Hello world" используется функция print, а не одноименный оператор. Да, для интерпретации сценариев дополнений LibreOffice используется интерпретатор Python 3.

Теперь осталось лишь создать в директории уровнем выше сценарий командной оболочки bash, который будет использоваться для сборки и тестирования дополнения. Содержимое данного сценария с комментариями приведено ниже:

#!/bin/bash

# Удаление предыдущей установленной версии дополнения
unopkg remove ruslinux.oxt
# Удаление файла предыдущей версии дополнения
rm ruslinux.oxt
# Переход в директорию с файлами дополнения
pushd ruslinux
# Упаковка файлов дополнения в архив формата zip с расширением .oxt
zip -r ../ruslinux.oxt *
# Возврат в ранее установленную рабочую директорию
popd
# Установка переменных окружения для записи отладочных сообщений в поток стандартного вывода
export PYUNO_LOGLEVEL=ARGS
export PYUNO_LOGTARGET=stdout
# Установка новой версии дополнения
unopkg add ruslinux.oxt
# Запуск текстового процессора для тестирования дополнения
oowriter

Используемая в сценарии для установки и удаления дополнения утилита unopkg находится в пакете программных компонентов для разработчиков LibreOffice SDK. Очевидно, что сам файл дополнения генерируется с помощью утилиты zip и отличается от обычного архива соответствующего формата лишь расширением .oxt и обязательным использованием алгоритма компрессии deflate. С помощью сценария устанавливаются значения двух переменных окружения: PYUNO_LOGLEVEL и PYUNO_LOGTARGET. С помощью первой переменной указывается, насколько подробными должны быть отладочные сообщения (допустимы значения NONE для деактивации режима отладки, CALL для вывода информации о вызовах функций и ARGS для вывода информации о вызовах функций с значениями их аргументов), с помощью второй - куда их следует передавать (допустимы значения stdout для записи сообщений в стандартный поток вывода, stderr для записи сообщений в стандартный поток ошибок, а также строки URL для записи данных в файлы). В данном случае активирован режим вывода самых подробных отладочных сообщений посредством стандартного потока вывода.

По сути, на этом этапе мы закончили подготовку окружения для разработки дополнения и впредь будем модифицировать лишь файл сценария ruslinux.py.

3. Проверка корректности создания окружения разработки дополнения

Для начала следует убедиться в корректности размещения файлов и работоспособности созданного шаблона дополнения. Дерево директорий рабочего окружения должно выглядеть следующим образом:

$ tree
.
├── build.sh
└── ruslinux
   ├── Addons.xcu
   ├── META-INF
   │   └── manifest.xml
   └── ruslinux.py

2 directories, 4 files

Если все файлы рабочего окружения были размещены и заполнены данными корректно, можно собрать и протестировать дополнение:

$ sh build.sh 
~/Документы/libreoffice_addon/ruslinux ~/Документы/libreoffice_addon
 adding: Addons.xcu (deflated 55%)
 adding: META-INF/ (stored 0%)
 adding: META-INF/manifest.xml (deflated 48%)
 adding: ruslinux.py (deflated 45%)
~/Документы/libreoffice_addon
2015-04-09 19:11:22,200 [CALL,tid 4]: Instantiating pyuno bridge
...

Теперь следует активировать появившийся пункт в меню LibreOffice Сервис-Дополнения. В результате вы должны увидеть в терминале аналогичный фрагмент вывода:

...
2015-04-09 19:11:29,889 [CALL,tid 1]: try     uno->py[0x-57d4ee14].trigger((string)"export")
Hello world
2015-04-09 19:11:29,890 [CALL,tid 1]: success uno->py[0x-57d4ee14].trigger()=void

Отлично, наша функция вызывается из меню LibreOffice.

4. Получение доступа к содержимому текстового документа

Теперь нужно получить доступ содержимому текущего текстового документа. Для этого сначала необходимо получить объект рабочего стола компонента с помощью следующего вызова:

desktop = self.ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", self.ctx)

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

document = desktop.getCurrentComponent()

Следует учесть, что текущим документом может оказаться не только текстовый документ, но и электронная таблица, презентация или диаграмма, поэтому следует проверить наличие у объекта документа свойства "Text" перед продолжением его обработки. Это делается следующим образом:

if not hasattr(document, "Text"):
	return

В том случае, если текст документа недоступен (открытый документ не является текстовым), исполнение сценария будет немедленно прекращено.

Посредством свойства "Text" объекта текущего документа можно получить доступ к объекту текста документа. У этого объекта есть метод getString(), который возвращает строку со всем текстом документа. Это не совсем то, что нужно, но на данном этапе упомянутая строка понадобится для тестирования работоспособности механизма записи данных в результирующий файл. Перед созданием результирующего файла следует выяснить путь, по которому этот файл будет сохранен. Для этой цели следует обратиться к свойству "URL" объекта текущего документа. Но в результате будет возвращен не путь к файлу, а специальная строка URL. Следует принимать в расчет и то, что в том случае, если документ не был сохранен, будет возвращена пустая строка. Для преобразования строки URL в строку с путем к файлу может использоваться функция fileUrlToSystemPath() из модуля uno, а для замены расширения файла может использоваться функция splitext() из модуля os.path. Для безопасной обработки случаев возврата пустых строк отлично подходит блок обработки исключений try/except. Исходя из вышесказанного, для получения пути к результирующему файлу может использоваться следующий блок кода:

import os.path
...
srcurl = document.URL
try:
	srcpath = uno.fileUrlToSystemPath(srcurl)
	htmlfilename = os.path.splitext(srcpath)[0] + '.html'
except:
	htmlfilename = 'output.html'

Очевидно, что в том случае, если текстовый документ не был сохранен, результирующий файл будет сохраняться под именем output.html в текущей рабочей директории. Теперь необходимо проверить непосредственную возможность создания результирующего файла и записи данных в него. Для открытия файла в режиме записи удобно использовать функцию open() из модуля io. Данная функция позволяет осуществлять прозрачную перекодировку символов при записи текстовых данных в файл, ведь по правилам сайта "Виртуальная энциклопедия "Linux по-русски" при сохранении документов формата HTML должна использоваться кодировка KOI8-R, а текстовый процессор использует кодировку UTF-8. Данный подход имеет и один недостаток, о котором мы поговорим позднее.

htmlfile = io.open(htmlfilename, 'w', encoding='koi8-r')

Для тестирования работоспособности сценария разумно использовать метод write() объекта результирующего файла, который позволяет записать в файл весь текст редактируемого текстового документа.

htmlfile.write(html.escape(document.Text.getString()))

Функция escape() из модуля html позволяет заменить все специальные символы HTML на их эквиваленты. В Python 2 для этой цели использовалась одноименная функция из модуля cgi. После записи данных результирующий файл должен быть корректно закрыт. В большинстве случаев будет достаточно вызова метода close() объекта файла, но для гарантированной записи всех данных также может быть вызван метод flush() того же объекта.

htmlfile.flush()
htmlfile.close()

Пришло время проверить работоспособность дополнения. Попробуйте набрать произвольный текст в текстовом процессоре Writer и активировать созданное дополнение. В результате активации дополнения в рабочей директории должен быть создан текстовый файл с расширением .html, причем для кодирования его символов должна использоваться кодировка KOI8-R. Если на этом этапе не возникло никаких проблем, можно переходить к разбору разметки документа.

5. Обработка документа

В текстовых документах LibreOffice/OpenOffice текст делится на параграфы, причем для каждого из параграфов может использоваться отличный стиль (который устанавливается из меню "Стили и форматирование"). В роли параграфов также могут выступать таблицы, иллюстрации, мультимедийные и встраиваемые объекты. Для обхода параграфов текстового документа необходимо создать объект перечисления элементов документа средствами объекта текста документа. Основными методами созданного объекта перечисления элементов документа являются методы hasMoreElements() и nextElement(), предназначенные для получения информации о наличии других объектов элементов документа в перечислении элементов документа и извлечения следующего объекта элемента документа из перечисления соответственно. Обход элементов документа целесообразно осуществлять в рамках цикла while, причем каждый из возвращаемых объектов будет иметь метод supportsService(), благодаря которому можно установить, является ли соответствующий элемент текстовым параграфом, таблицей или объектом другого типа. Список наиболее часто используемых аргументов упомянутого метода приведен в Таблице 1.

Таблица 1 - Аргументы метода supportsService()

Аргумент Элемент документа
com.sun.star.text.Paragraph Текстовый абзац
com.sun.star.text.TextTable Таблица

Англоязычное имя стиля параграфа хранится в свойстве ParaStyleName объекта параграфа. Некоторые из англоязычных и соответствующих им русскоязычных имен стилей параграфов приведены в Таблице 2.

Таблица 2 - Имена стилей параграфов

Англоязычное имя стиля параграфа Русскоязычное имя стиля параграфа
Preformatted Text Текст в заданном формате
Frame contents Содержимое врезки
Heading Заголовок
Standard Базовый

Аналогичный подход может быть применен и к возвращаемым объектам текстовых параграфов для получения информации об их специальном форматировании. Параметры форматирования фрагментов текстовых параграфов задаются с помощью свойств соответствующих объектов, причем некоторые из этих свойств являются необязательными. В Таблице 3 приведен список наиболее часто используемых свойств объектов фрагментов параграфов.

Таблица 3 - Свойства объектов фрагментов параграфов

Имя свойства Описание Значение Обязательное свойство
CharStrikeout Зачеркнутый текст Логическое bool (true, false) Нет
CharUnderline Подчеркнутый текст Целочисленное short (NONE = 0, SINGLE = 1, DOUBLE = 2, DOTTED = 3, DONTKNOW = 4, DASH = 5, LONGDASH = 6, DASHDOT = 7, DASHDOTDOT = 8, SMALLWAVE = 9, WAVE = 10, DOUBLEWAVE = 11, BOLD = 12, BOLDDOTTED = 13, BOLDDASH = 14, BOLDLONGDASH = 15, BOLDDASHDOT = 16, BOLDDASHDOTDOT = 17, BOLDWAVE = 18) Да
CharWeight Жирный текст С плавающей точкой float (DONTKNOW = 0.00, THIN = 50.00, ULTRALIGHT = 60.00, LIGHT = 75.00, SEMILIGHT = 90.00, NORMAL = 100.00, SEMIBOLD = 110.00, BOLD = 150.00, ULTRABOLD = 175.00, BLACK = 200.00) Да
CharPosture Наклонный текст Перечисление (NONE, OBLIQUE, ITALIC, DONTKNOW, REVERSE_OBLIQUE, REVERSE_ITALIC) Да
CharShadowed Текст с тенью Логическое (true, false) Нет
HyperLinkURL Текст с гиперссылкой Строковое (строка URL) Нет

Исходя из всего вышесказанного код для формирования результирующего документа формата HTML будет выглядеть следующим образом:

Для лучшего понимания данный код обрабатывает лишь один стиль параграфа "Heading", который соответствует заголовку. Код сценария из листинга в конце статьи будет дополнен с целью обработки стилей параграфов "Preformatted Text" и "Frame contents", соответствующих созданию многострочного листинга и помещению текста в поле прокрутки соответственно. Специальное форматирование фрагментов текста параграфов обрабатывается в полном объеме.

6. Обработка списков

В текстовых документах нередко встречаются списки, которые также могут быть перенесены в результирующий документ формата HTML. Обработка списков осложняется несколькими обстоятельствами: во-первых, на этапе обработки документа средствами сценария очень сложно сделать вывод о том, является ли список упорядоченным или нет, поэтому мы будем считать, что все списки в текстовых документах являются упорядоченными; во-вторых, текстовый процессор позволяет начать нумерацию в любой части списка, что невозможно в HTML, поэтому в случае использования данной возможности нумерация в соответствующем списке из результирующего документа формата HTML будет отличаться от оригинала. Каждый элемент списка представлен в рамках сценария с помощью объекта параграфа, причем значением свойства "NumberingStyleName" этого объекта является непустая строка. Уровень списка, на котором расположен рассматриваемый элемент, устанавливается с помощью значения свойства "NumberingLevel" соответствующего объекта, а начальное значение списка - с помощью значений свойств "NumberingStartValue" объектов каждого из элементов. Ниже приведен фрагмент кода, позволяющий сформировать списки с учетом уровней и начальных значений в результирующем документе формата HTML:

7. Обработка таблиц

В код сценария не помешает добавить блок, предназначенный для обработки таблиц текстовых документов. Обработка таблиц текстовых документов не является такой сложной задачей, как может показаться на первый взгляд. После идентификации объекта параграфа как таблицы для доступа к ее данным могут использоваться методы getRows() и getColumns(), которые позволяют получить объекты строк и столбцов соответственно. Оба упомянутых объекта поддерживают метод getCount(), благодаря которому можно получить информацию о количестве строк и столбцов в таблице. Непосредственный доступ к значениям из ячеек таблицы может осуществляться посредством объектов соответствующих ячеек таблицы. Для получения объекта ячейки таблицы используется метод getCellByName() представляющего таблицу объекта параграфа текстового документа, но для работы с этим методом придется определить имя интересующей ячейки таблицы. При определении имени ячейки таблицы следует помнить о том, что имена ячеек таблиц состоят из буквенного обозначения столбца и цифрового обозначения строки таблицы. Первая ячейка таблицы всегда носит имя A1. В случае большого количества столбцов могут использоваться несколько букв (к примеру, ячейка из 27 столбца и первой строки таблицы будет носить имя AA1). В разрабатываемом сценарии обход ячеек таблиц осуществляется с помощью следующего кода:

8. Проблема с кодировками

С помощью приведенного выше кода могут вполне успешно обрабатываться простые документы без некоторых символов Unicode. Если же вы используете в документе, к примеру, символ дефиса (-), его обработка не будет осуществляться в полном объеме, а в терминале будет выводиться отладочное сообщение об ошибке в процессе перекодировки символов, аналогичное следующему:

После изучения отладочного сообщения можно сделать вывод о том, что в таблице символов кодировки KOI8-R попросту не находится символа, соответствующего дефису, поэтому генерируется соответствующее исключение. Это исключение не обрабатывается должным образом, следовательно, работа сценария завершается. По сути данное исключение и не следует обрабатывать; нужно избежать его генерации. К счастью, функция open() из модуля io имеет необязательный параметр errors, с помощью которого можно указать имя зарегистрированного обработчика ошибок перекодировки символов. Регистрация упомянутого обработчика осуществляется с помощью функции register_error() из модуля codecs (разумеется, этот модуль необходимо предварительно импортировать). Обработчик будет принимать объект исключения типа UnicodeEncodeError, содержащий фрагмент строки, перекодировку которого не удалось осуществить, а также данные о расположении этого фрагмента в строке. В рамках обработчика может как генерироваться новое исключение, так и осуществляться возврат кортежа со строкой для замены проблемного фрагмента и указателем позиции для продолжения перекодировки. В приведенном ниже коде обработчика осуществляется замена наиболее часто встречающихся в документах специальных символов на их эквиваленты из таблицы символов кодировки KOI8-R:

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

9. Информация о документе

На данном этапе сценарий будет обрабатывать весь текст любого редактируемого с помощью текстового процессора Writer документа, хотя в некоторых случаях форматирование результирующего документа формата HTML и может оказаться некорректным. Единственным принципиальным отличием результирующего документа от шаблона оформления материалов для публикации на сайте "Виртуальная энциклопедия "Linux по-русски" является отсутствие блока с информацией о документе. К тому же, в результирующем документе формата HTML не используются стили, описанные в упомянутом шаблоне. Придется исправить это несоответствие.

Для ввода информации о документе может использоваться диалог изменения свойств документа текстового процессора Writer. На вкладке "Описание" имеет смысл задействовать поля "Заголовок", "Тема" и "Ключевые слова". Данная информация доступна на уровне сценария благодаря существованию объекта свойств документа DocumentProperties. В частности, строка, введенная в поле "Заголовок" доступна посредством свойства "Title" упомянутого объекта, строка, введенная в поле "Тема" - посредством свойства "Subject", а набор ключевых слов, введенных через запятую в поле "Ключевые слова" - посредством свойства "Keywords". Имя переводчика устанавливается с помощью диалога изменения параметров текстового процессора и доступно посредством свойства "Author" упомянутого объекта, а дата создания документа устанавливается автоматически и доступна посредством свойства "CreationDate" этого же объекта. На вкладке "Свойства пользователя" может быть введена дополнительная информация об оригинале документа, а именно, имя автора, дата создания, название и строка URL. Каждое из пользовательских свойств может иметь произвольное имя и один из допустимых типов. В сценарии будут использоваться заданные имена свойств "X-Original-Author", "X-Oriinal-Date", "X-Original-Name" и "X-Original-URL", причем значения всех параметров, за исключением даты создания оригинала документа, будут задаваться в строковом формате. Функции для преобразования даты в строковый формат, сравнения строк без учета регистра и блок кода для записи заголовка с информацией о документе выглядят следующим образом:

На этом разработка дополнения может быть завершена. Подводя итог, следует дать пояснения относительно функций разработанного дополнения. Помимо того, что дополнение позволяет сохранять редактируемый текстовый документ в виде документа формата HTML, использующего кодировку KOI8-R, оно также позволяет сохранить в результирующем документе форматирование отдельных фрагментов параграфов (жирный текст, наклонный текст, подчеркнутый текст, перечеркнутый текст), воссоздавать гиперссылки, выделять произвольные фрагменты параграфов (отформатированные как текст с тенью на уровне текстового процессора), воссоздавать списки (с ограничениями), формировать заголовки (при использовании стиля "Заголовок"), многострочные листинги (при использовании стиля "Текст в заданном формате"), помещать текст в область с прокруткой (при использовании стиля "Содержимое врезки"), а также формировать блок с информацией о документе и его оригинале. В листинге ниже приведен полный исходный код основного сценария дополнения ruslinux.py с комментариями для лучшего понимания. Данный исходный код также незначительно дополнен с целью добавления возможности создания многострочных листингов и помещения текста в область прокрутки. Кроме того, все файлы дополнения помещены в данный архив.

Заключение

Несмотря на то, что API дополнений офисного пакета LibreOffice является достаточно сложным, разработка дополнений для выполнения таких задач, как экспорт или модификация редактируемых документов, не представляет особых сложностей ввиду наличия множества примеров на ресурсах, посвященных как офисному пакету LibreOffice, так и офисному пакету OpenOffice. Дополнение, разработанное для одного из упомянутых офисных пакетов, может впоследствии использоваться совместно с другим офисным пакетом с минимальными модификациями. Благодаря поддержке множества языков программирования (Java, Python 3, C++, Star Basic), разработка дополнений может осуществляться как профессиональными программистами, так и пользователями, имеющими поверхностное представление о программировании.


Эта статья еще не оценивалась
Вы сможете оценить статью и оставить комментарий, если войдете или зарегистрируетесь.
Только зарегистрированные пользователи могут оценивать и комментировать статьи.

Комментарии отсутствуют