Pull to refresh

PyUNO — быстрое незначительное редактирование xls-отчета из Python

Reading time 3 min
Views 13K

Просто и быстро


Не так давно я столкнулся с необходимостью запротоколировать список изменений в нашем ПО. Заказчик прислал мне формуляр, который я должен был заполнить в соответствии с их внутренними требованиями к документации. Я открыл прилагавшийся к письму файл «Изменения 1.xls» и немного приуныл. Точнее, мне в голову последовательно пришли мысли об увольнении, а затем — о самоубийстве. Формуляр состоял из 14 колонок. Быстро перемножив в уме количество колонок с числом внесенных нами атомарных изменений (около пятисот), я пошел курить.
Сделать такую работу руками мне не под силу. Большинство данных (номера новых версий, описания изменений и т. п.) у меня, конечно, имелись в наличии, но в разных местах и самых причудливых форматах. Но семьсот копипастов — увольте. Поэтому мне пришлось немного освоить PyUNO. На всякий случай — опишу вкратце процесс управления документом OOo из питоновского биндинга, вдруг кому пригодится.

Нам потребуются сам Open Office, python и, собственно, биндинги:
$ sudo yum search pyuno ure

Запускаем OOo (нам нужны электронные таблицы, но метод работает для всех приложений — см. пример 2) с включенной опцией «слушать сокет»:
$ oocalc "-accept=socket,host=localhost,port=8100;urp;" &
$ soffice "-accept=socket,host=localhost,port=8100;urp;" -writer -headless &

…и отправляемся писать код. Для начала — получим инстанс документа:
import uno
from os.path import abspath, isfile, splitext

def getDocument(inputFile) :
  localContext = uno.getComponentContext()
  resolver = localContext.ServiceManager.createInstanceWithContext( \
    "com.sun.star.bridge.UnoUrlResolver", localContext)
  try:
    context = resolver.resolve( \
    "uno:socket,host=localhost,port=%s;urp;StarOffice.ComponentContext" % 8100)
  except NoConnectException:
    raise Exception, "failed to connect to OpenOffice.org on port %s" % 8100
  desktop = context.ServiceManager.createInstanceWithContext( \
    "com.sun.star.frame.Desktop", context)
  document = desktop.loadComponentFromURL( \
    uno.systemPathToFileUrl(abspath(inputFile)), "_blank", 0, tuple([]))

Получение данных для заполнения итогового документа из текстовых файлов и change-логов я оставлю за рамками данной заметки. Пусть они у нас просто появляются в псевдополе data по мановению волшебной палочки. Итак, приступим к заполнению нашего документа (заполняем столбец №2):
from com.sun.star.beans import PropertyValue

def fillDocument(inputFile, col, data) :
  try:
    sheet = getDocument(inputFile).getSheets().getByIndex(0)
    row = 2
    while True:
      row = row + 1
      val = sheet.getCellByPosition(col, row).getFormula()
      if val != '' :
        sheet.getCellByPosition(col, row).setFormula(val.replace(%VERSION%, data))
      else :
        break;
    '''
    All the rows are now filled, It's time to save our modified document
    '''
    props = []
    prop = PropertyValue()
    prop.Name = "FilterName"
    prop.Value = "MS Excel 97"
    props.append(prop)
    document.storeToURL(uno.systemPathToFileUrl(abspath(inputFile)) + ".out.xls", tuple(props))
  finally:
    document.close(True)

Точно так же можно обойтись с любым другим столбцом. Можно даже в дороге сходить в Google Translate за переводом.

Еще некоторые полезные возможности


Вставка другого документа


'''
Required for Ctrl+G :-)
'''
from com.sun.star.style.BreakType import PAGE_BEFORE, PAGE_AFTER

def addAtTheEnd(inputFile) :
  cursor.gotoEnd(False)
  cursor.BreakType = PAGE_BEFORE

  cursor.insertDocumentFromURL(uno.systemPathToFileUrl(abspath(inputFile)), ())

Поиск и замена


def findAndReplace(pattern, substTo, replaceAll, caseSensitive) :
  search = document.createSearchDescriptor()
  search.SearchRegularExpression = True
  search.SearchString = pattern
  search.SearchCaseSensitive = caseSensitive

  result = document.findFirst(search)
  while found:
    result.String = string.replace(result.String, pattern, substTo)
    if not replaceAll :
      break
    result = document.findNext(result.End, pattern)

Экспорт в PDF


def exportToPDF(outputFile)
  props = []
  prop = PropertyValue()
  prop.Name = "FilterName"
  prop.Value = "writer_pdf_Export"
  props.append(prop)
  document.storeToURL(uno.systemPathToFileUrl(abspath(outputFile)), tuple(props))

Disclaimer


Сразу хочу оговориться: код претендует на звание наколеночного говнокода, выполняемого один раз. Но в качестве «ну-ка быстро обновим файл отчета» — мне лично сэкономил уже кучу времени.

— Вот тут и вокруг можно собрать еще какие-то обрывки информации: http://wiki.services.openoffice.org/wiki/Uno/FAQ
— Шаблонизатор на питоне для OOo: appyframework.org
— То же самое, только для C++: habrahabr.ru/blogs/cpp/116228

Upd: В комментариях подсказывают другой способ: xlwt.
Upd2: Для генерации в более «новомодный» формат xlsx, есть такая полезная либа: xlsx.dowski.com.
За оба дополнения — огромная благодарность tanenn.
Tags:
Hubs:
+28
Comments 20
Comments Comments 20

Articles