Переход с Basic на Python-UNO

Для реализации переноса среды программирования с Basic на Python можно использовать следующие модули.


Используемые модули


import uno
 
import unohelper

Контекст сценария

В макросе Python для AlterOffice вы можете использовать переменную XSCRIPTCONTEXT из модуля com.sun.star.script.provider.XScriptContext, которая предоставляет следующие интерфейсы:
  • com.sun.star.frame.XModel: возвращает текущий объект документа.
  • com.sun.star.document.XScriptInvocationContext: возвращает зависимый от вызова объект.
  • com.sun.star.frame.XDesktop: возвращает экземпляр службы com.sun.star.frame.Desktop.
  • com.sun.star.uno.XComponentContext: возвращает текущий контекст компонента, который используется для указания контекста.
Обратите внимание, что переменная XSCRIPTCONTEXT определена на уровне модуля и не доступна для модулей, импортированных из вашего скрипта.

Контекст компонента

В Basic вы можете получить контекст компонента следующим образом:

ctx = GetDefaultContext()

В Python вы можете получить доступ к контексту компонента через контекст скрипта.

ctx = XSCRIPTCONTEXT.getComponentContext()

Это значение необходимо для создания экземпляров служб.

Менеджер сервисов

В Basic вы можете получить менеджер сервисов следующим образом:

smgr = GetProcessServiceManager()

В Python этот экземпляр является наиболее важным значением для создания экземпляров служб для работы с офисными API.

ctx = XSCRIPTCONTEXT.getComponentContext()
smgr = ctx.getServiceManager()

Для создания экземпляров служб вам нужен как контекст компонента, так и диспетчер служб.

Экземпляр службы

В Basic вы можете создавать службы с помощью встроенной функции следующим образом:

sfa = CreateUnoService("com.sun.star.ucb.SimpleFileAccess")

Или с инициализацией аргументов:

arg = com.sun.star.ui.dialogs.TemplateDescription.FILESAVE_SIMPLE
file_picker = CreateUnoServiceWithArguments("com.sun.star.ui.dialogs.FilePicker", Array(arg))

В Python вы можете работать с интерфейсом com.sun.star.lang.XMultiComponentFactory следующим образом:

ctx = XSCRIPTCONTEXT.getComponentContext()
smgr = ctx.getServiceManager()
sfa = smgr.createInstanceWithContext("com.sun.star.ucb.SimpleFileAccess", ctx)

Если вам нужно инициализировать экземпляр, используйте метод createInstanceWithArgumentsAndContext:

from com.sun.star.ui.dialogs.TemplateDescription import FILESAVE_SIMPLE
file_picker = smgr.createInstanceWithArgumentsAndContext("com.sun.star.ui.dialogs.FilePicker", (FILESAVE_SIMPLE,) ctx)

или инициализировать после создания экземпляра:

file_picker = smgr.createInstanceWithContext("com.sun.star.ui.dialogs.FilePicker", ctx)
file_picker.initialize((FILESAVE_SIMPLE,))

Сервис с конструктором

В Basic вы можете вызывать сервисную конструкцию из ее модуля:

shell_execute = com.sun.star.system.SystemShellExecute.create()

В Python вы должны создать его экземпляр с помощью метода XMultiComponentFactory::createInstanceWithArgumentsAndContext с начальными аргументами или создать экземпляр после создания.
Вызов конструктора выполняет проверку типов перед передачей аргументов в метод createInstanceWithArgumentsAndContext.

Текущий документ

В Basic функция ThisComponent во время выполнения обеспечивает доступ к текущему документу:

doc = ThisComponent

В Python вы можете получить доступ к текущему документу через контекст скрипта:

doc = XSCRIPTCONTEXT.getDocument()

Если вы создали макрос внутри конкретного документа, то объект документа, к которому относится макрос, будет соответствовать этому документу. Если же макрос находится в пользовательской библиотеке или общей библиотеке макросов, то объект документа, к которому обращается макрос, будет находиться в активном окне.

Рабочий стол

В Basic, StarDesktop предоставляют функцию выполнения во время работы:

desktop = StarDesktop()

В Python вы можете получить доступ к рабочему столу через контекст скрипта:

desktop = XSCRIPTCONTEXT.getDesktop()

Экземпляр структуры

В Basic экземпляр структуры может быть создан двумя способами:

Dim a As New com.sun.star.awt.Point
a = CreateUnoStruct("com.sun.star.awt.Point")

В Python вы можете использовать следующие способы создания экземпляра структуры. Импортируйте класс структуры и вызовите его.

from com.sun.star.awt import Point

a = Point() # создать экземпляр со значениями по умолчанию, X=0, Y=0
b = Point(100, 200) # инициализировать с начальными значениями, X=100, Y=200

Вызывая класс для создания нового экземпляра структуры, вы можете очистить его аргументы или передать значения для всех полей. Другими словами, вы не можете передать недостаточное количество аргументов для инициализации. И его порядок должен совпадать с определением структуры в его IDL. Например, экземпляр структуры b, имеющий X=100 и Y=200 в приведенном выше фрагменте кода.
Вы можете инициализировать без импорта класса вашей целевой структуры с помощью функции uno.createUnoStruct следующим образом:

import uno
a = uno.createUnoStruct("com.sun.star.awt.Point")
b = uno.createUnoStruct("com.sun.star.awt.Point", 100, 200)

Это дает тот же результат, что и в приведенном выше примере. Первый параметр метода createUnoStruct — это имя инициализируемой структуры. Следующие аргументы являются начальными значениями для нового экземпляра.


Enum

В Basic вы можете получить доступ к модулю enum следующим образом:

ITALIC = com.sun.star.awt.FontSlant.ITALIC

В Python можно использовать следующие способы:

import uno
 
from com.sun.star.awt.FontSlant import ITALIC
ITALIC = uno.getConstantByName("com.sun.star.awt.FontSlant.ITALIC")
ITALIC = uno.Enum("com.sun.star.awt.FontSlant", "ITALIC")

Данный код позволяет получить доступ к модулю enum в Python при работе с библиотекой UNO и определить перечисляемый тип ITALIC с помощью методов getConstantByName и Enum.

Константы

В Basic вы можете получить доступ к константам через модуль:

BOLD = com.sun.star.awt.FontWeight.BOLD

В Python предусмотрены следующие способы:

import uno
from com.sun.star.awt.FontWeight import BOLD
BOLD = uno.getConstantByName("com.sun.star.awt.FontWeight.BOLD")

В этом примере импортируется и устанавливается константа BOLD в модуле com.sun.star.awt.FontWeight с помощью функции getConstantByName из модуля uno.

Последовательность

Последовательность - это набор значений одного типа, который можно обойти в цикле. В языке Basic для этого используется массив, а в Python - кортеж. Список в Python не является подходящим для передачи в качестве значения последовательности.

Строка

Строка Python может содержать более 64K байт.
Если вам нужно написать в сценарии 7-битные символы, отличные от Ascii, напишите комментарий в начале файла. Это стандартные инструкции Python.

# -*- coding: utf_8 -*-

Пожалуйста, прочтите документацию Python для более подробной информации.


Char

В Basic нет специального значения для типа char. В Python класс uno.Char определен для типа char.

import uno
c = uno.Char("a")

Тип

"Type" это метатип UNO, представляющий тип UNO. Начиная с версии 3.2, если вы передаете методу строковое значение в качестве аргумента, который должен быть типом, мост Basic считывает его как тип.

oMap = com.sun.star.container.EnumerableMap.create("string", "string")

В Python для создания нового значения типа можно использовать следующие способы.

import uno
t = uno.getTypeByName("string")
t = uno.Type("string", uno.Enum("com.sun.star.uno.TypeClass", "STRING"))

Последовательность байтов

Последовательность байтов - это набор чисел, каждое из которых занимает один байт. В Basic для хранения такой последовательности можно использовать массив байтов.
В Python такую последовательность можно представить в виде строки str, которая обернута в класс uno.ByteSequence. Если вы получаете из UNO какую-то последовательность байтов, то она будет представлена в виде объекта типа uno.ByteSequence. Чтобы получить реальное значение этой последовательности, нужно обратиться к значению переменной объекта.

Исключение

В Basic вы получаете исключение как некоторую ошибку. И для ее перехвата используется оператор On Error.

Sub ErrorExample

  On Error GoTo Handler

  ' ... error

  Exit Sub

  Handler:

End Sub

В Python исключение, созданное в UNO, можно рассматривать как обычное исключение Python. Вот пример:

from com.sun.star.container import IndexOutOfBoundsException
try:
   obj.getByIndex(100) # raises IndexOutOfBoundsException
except IndexOutOfBoundsException as e:
   print(e)

Если метод getByIndex вызывает IndexOutOfBoundsException, его можно перехватить в операторе exclude, поскольку все исключения наследуют класс Python Exception.
А также вы можете выдать исключение UNO из своего кода Python следующим образом:
from com.sun.star.uno import RuntimeException
raise RuntimeException("Some message", None)

Пустое значение

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

None - это специальный объект-сентинель в Python, который используется для обозначения отсутствия значения или недействительности. В UNO None используется, чтобы указать на недопустимый или отсутствующий объект, а также в качестве возвращаемого значения методов типа void. Это может быть полезно при передаче аргументов в методы, которые могут принимать нулевые значения. Однако, результат такой передачи зависит от реализации метода.

Контейнеры

Объект контейнера в Basic представляет собой некий список или массив, который можно обходить поочередно для доступа к его элементам.
В Python такой ярлык, как в Basic, не используется. Вместо этого для доступа к элементам индексируемого контейнера в Python используются циклы, в которых генерируются последовательные индексы с помощью функции range(). Таким образом, можно последовательно обойти все элементы контейнера и выполнить необходимые операции с каждым из них.

for i in range(container.getCount()):
obj = container.getByIndex(i)

URL-адрес и системный путь

import uno
 
path = "/home/foo/Documents/file.odt"
url = uno.systemPathToFileUrl(path)
path = uno.fileUrlToSystemPath(url)

Аргументы и возвращаемое значение

В Basic режим первого аргумента метода parseStrict - "in out" в следующем коде:

aURL = CreateUnoStruct("com.sun.star.util.URL")
aURL.Complete = ".uno:Paste"
CreateUnoService("com.sun.star.util.URLTransformer").parseStrict(aURL)

Содержимое переменной aURL обновляется после вызова метода.
В Python параметр out mode возвращается как часть возвращаемого значения.

from com.sun.star.util import URL
 
aURL = URL()
aURL.Complete = ".uno:Paste"
dummy, aURL = smgr.createInstanceWithContext("com.sun.star.util.URLTransformer", ctx).parseStrict(aURL)
# Definition of com.sun.star.util.XURLTransformer::parseStrict method:
# void parseStrict([inout] com.sun.star.util.URL aURL);

Если метод имеет режим out в своих параметрах, его возвращаемым значением всегда является кортеж, содержащий исходное возвращаемое значение и значения для параметров out. Вот потенциальный пример:

# boolean getSomeValue([in] string aName, [out] short aNum, [inout] long aNum2);
result, num, num2 = obj.getSomeValue("foo", 100, 200)

В данном примере исходное возвращаемое значение передается в переменную результата, а значения второго и третьего параметров передаются в переменные num и num2 соответственно. Хотя второй параметр метода принимает значение 100, оно не используется в методе в качестве входного значения. Также стоит отметить, что возвращаемый кортеж не содержит записи для параметра in mode.

Функции для выполнения

В Basic вы не можете выбирать подпрограммы, которые будут выполняться пользователями.
Определите переменную g_exportedScripts, которая содержит кортеж callable в вашем файле макроса.

def func_a(): pass
def func_b(): pass
def func_hidden(): pass # not shown in the UI
g_exportedScripts = func_a, func_b

В приведенном выше коде func_hidden не отображается в диалоговом окне выполнения макросов.


Импорт модулей

В Basic вы можете использовать подпрограммы из других модулей или библиотек, которые были загружены.

BasicLibraries.loadLibrary("Library1")
Library1.Foo()

В языке Python вы можете импортировать модули, которые находятся в списке sys.path. Если вы хотите использовать свой собственный модуль, который находится в каталоге Scripts/python, поместите его в каталог pythonpath рядом со своим скриптом.

- Scripts/
 - python/
   - macro.py
   - pythonpath/  # этот каталог добавляется автоматически перед выполнением вашего макроса
     - your_module.py  # этот модуль можно найти

Когда вы запускаете макрос из своего скрипта, внутренний исполнитель автоматически добавляет каталог pythonpath/ в список sys.path, чтобы вы могли использовать его для поиска модулей.
Обратите внимание, что имена модулей могут конфликтовать друг с другом, если у вас есть модуль с одинаковым именем в каком-то месте. Это стандартный механизм импорта модулей в Python. Чтобы избежать конфликта имен, поместите каждый модуль в библиотеку с уникальным именем.

Диалоговое окно

В Basic существует функция CreateUnoDialog runtime для создания экземпляра диалогового окна.

DialogLibraries.loadLibrary("Standard")
dialog = CreateUnoDialog(DialogLibraries.Standard.Dialog1)
dialog.execute()
dialog.dispose()

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

def dialog_example():
   ctx = XSCRIPTCONTEXT.getComponentContext()
   smgr = ctx.getServiceManager()
   dp = smgr.createInstanceWithContext("com.sun.star.awt.DialogProvider", ctx)
   dialog = dp.createDialog("vnd.sun.star.script:Standard.Dialog1?location=user")
   dialog.execute()
   dialog.dispose()


Окно сообщения

В Basic вы можете использовать функцию MsgBox runtime для отображения сообщения пользователям.

Msgbox "Hello."

В Python функция быстрого доступа не предусмотрена, но вы можете использовать интерфейс через com.sun.star.awt.XMessageBoxFactory.

def messagebox(ctx, parent, message, title, message_type, buttons):
   """ Show message in message box. """
   toolkit = parent.getToolkit()
   older_imple = check_method_parameter(
       ctx, "com.sun.star.awt.XMessageBoxFactory", "createMessageBox",
       1, "com.sun.star.awt.Rectangle")
   if older_imple:
       msgbox = toolkit.createMessageBox(
           parent, Rectangle(), message_type, buttons, title, message)
   else:
       message_type = {"messbox": 0, "infobox": 1, "warningbox": 2, "errorbox": 3, "querybox": 4}[message_type]
       msgbox = toolkit.createMessageBox(
           parent, message_type, buttons, title, message)
   n = msgbox.execute()
   msgbox.dispose()
   return n
 
 
def check_method_parameter(ctx, interface_name, method_name, param_index, param_type):
   """ Check the method has specific type parameter at the specific position. """
   cr = create_service(ctx, "com.sun.star.reflection.CoreReflection")
   try:
       idl = cr.forName(interface_name)
       m = idl.getMethod(method_name)
       if m:
           info = m.getParameterInfos()[param_index]
           return info.aType.getName() == param_type
   except:
       pass
   return False

Поле ввода

Для Python не предусмотрена функция, вы можете создать самостоятельно. Вот пример:

def inputbox(message, title="", default="", x=None, y=None):
   """ Shows dialog with input box.
       @param message message to show on the dialog
       @param title window title
       @param default default value
       @param x dialog positio in twips, pass y also
       @param y dialog position in twips, pass y also
       @return string if OK button pushed, otherwise zero length string
   """
   WIDTH = 600
   HORI_MARGIN = VERT_MARGIN = 8
   BUTTON_WIDTH = 100
   BUTTON_HEIGHT = 26
   HORI_SEP = VERT_SEP = 8
   LABEL_HEIGHT = BUTTON_HEIGHT * 2 + 5
   EDIT_HEIGHT = 24
   HEIGHT = VERT_MARGIN * 2 + LABEL_HEIGHT + VERT_SEP + EDIT_HEIGHT
   import uno
   from com.sun.star.awt.PosSize import POS, SIZE, POSSIZE
   from com.sun.star.awt.PushButtonType import OK, CANCEL
   from com.sun.star.util.MeasureUnit import TWIP
   ctx = uno.getComponentContext()
   def create(name):
       return ctx.getServiceManager().createInstanceWithContext(name, ctx)
   dialog = create("com.sun.star.awt.UnoControlDialog")
   dialog_model = create("com.sun.star.awt.UnoControlDialogModel")
   dialog.setModel(dialog_model)
   dialog.setVisible(False)
   dialog.setTitle(title)
   dialog.setPosSize(0, 0, WIDTH, HEIGHT, SIZE)
   def add(name, type, x_, y_, width_, height_, props):
       model = dialog_model.createInstance("com.sun.star.awt.UnoControl" + type + "Model")
       dialog_model.insertByName(name, model)
       control = dialog.getControl(name)
       control.setPosSize(x_, y_, width_, height_, POSSIZE)
       for key, value in props.items():
           setattr(model, key, value)
   label_width = WIDTH - BUTTON_WIDTH - HORI_SEP - HORI_MARGIN * 2
   add("label", "FixedText", HORI_MARGIN, VERT_MARGIN, label_width, LABEL_HEIGHT,
       {"Label": str(message), "NoLabel": True})
   add("btn_ok", "Button", HORI_MARGIN + label_width + HORI_SEP, VERT_MARGIN,
           BUTTON_WIDTH, BUTTON_HEIGHT, {"PushButtonType": OK, "DefaultButton": True})
   add("btn_cancel", "Button", HORI_MARGIN + label_width + HORI_SEP, VERT_MARGIN + BUTTON_HEIGHT + 5,
           BUTTON_WIDTH, BUTTON_HEIGHT, {"PushButtonType": CANCEL})
   add("edit", "Edit", HORI_MARGIN, LABEL_HEIGHT + VERT_MARGIN + VERT_SEP,
           WIDTH - HORI_MARGIN * 2, EDIT_HEIGHT, {"Text": str(default)})
   frame = create("com.sun.star.frame.Desktop").getCurrentFrame()
   window = frame.getContainerWindow() if frame else None
   dialog.createPeer(create("com.sun.star.awt.Toolkit"), window)
   if not x is None and not y is None:
       ps = dialog.convertSizeToPixel(uno.createUnoStruct("com.sun.star.awt.Size", x, y), TWIP)
       _x, _y = ps.Width, ps.Height
   elif window:
       ps = window.getPosSize()
       _x = ps.Width / 2 - WIDTH / 2
       _y = ps.Height / 2 - HEIGHT / 2
   dialog.setPosSize(_x, _y, 0, 0, POS)
   edit = dialog.getControl("edit")
   edit.setSelection(uno.createUnoStruct("com.sun.star.awt.Selection", 0, len(str(default))))
   edit.setFocus()
   ret = edit.getModel().Text if dialog.execute() else ""
   dialog.dispose()
   return ret

Эта функция может быть вызвана следующим образом:
pnputbox("Please input some value", "Input", "Default value")

Выполнение макросов с помощью кнопок панели инструментов

В Basic вы можете вызывать любые подпрограммы или функции с помощью кнопок на панели инструментов.

Sub WriteHello()
 ThisComponent.getText().getEnd().setString("Hello!")
End Sub

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

def writeHello(*args):
   # writeHello(arg): тоже нормально, но если вы хотите вызвать эту функцию
   # каким-то образом определите, что ваша функция принимает переменные аргументы.
   XSCRIPTCONTEXT.getDocument().getText().getEnd().setString("Hello!")