Работа с LibreOffice, как с Com-объектом

Автор Борис_С, 22 октября 2021, 17:15

0 Пользователи и 1 гость просматривают эту тему.

economist

Взгляд со стороны на решаемую проблему: Рендеринг больших документов кодом через COM/UNO - часто сводится к работе с готовыми ODT/HybridPDF/DOC/DOCX-файлами-шаблонами и банальной замене в них уже имеющихся "полей" - не объектов, а обычных строк-местозаполнителей вида <Покупатель>, <ИНН>, <СпецификацияСчетПриложение>.

Такой "заменяющий" код - получается очень простым на любом языке (особенно на Python) и быстро работает в --headless режиме LO на сервере (да и в обычном режиме можно - документ открывается всего на пару секунд, если сервер работает под GUI).

Копаться же с редко используемыми API-методами типа процедурного создания ссылок/форматов/врезок или со вставкой и параметрами графики - это жутко сложно и займет месяцы.

Writer имеет прекрасный проверенный десятилетиями механизм связывания разделов с файлами на диске - ODT- и TXT-формата (содержимое TXT будет с одинаковым, но любым нужным форматированием, прямо как дипломные работы по РОСТу).

Также Writer прекрасно обновляет графику, вставленную как Связь. Если нужно изменить документ - просто перезапишите на любом языке программирования эти внешние файлы, и получите целый документ. Для обновления гибридного PDF его придется открыть и сохранить, но это и так происходит почти всегда.

Если же полностью рендерить ODT/DOC - для Python хороший пример вложен в libreoffice\share\Scripts\python\pythonSamples\TableSample.py и полно примеров кода в Сети.
Руб. за сто, что Питоньяк
Любит водку и коньяк!
Потому что мне, без оных, -
Не понять его никак...

mikekaganski

#46
Цитата: Борис_С от  2 декабря 2021, 19:01
На Basic это все давно реализовано и работает.

Да не может это быть реализовано и работать на Basic! Или реализовано не "это", или я вообще не понимаю, о чём речь.
Я сделал маленький документ Calc с гиперссылкой в A1. Сделал макрос IteratePortions, который реализует то, что показано в ответах 11, 19 и 22. Естественно, Basic точно так же даёт ошибку несуществующего свойства - показывая, что оно работает одинаково независимо от языка.

Так что у меня всё ещё остался вопрос: что же такое реализовано и работает на Basic, что не работает на C#? И я всё ещё считаю, что мой совет из ответа 35 имеет смысл.
С уважением,
Михаил Каганский

mikekaganski

А вот это работает.
С уважением,
Михаил Каганский

sokol92

Борис, Вы вряд ли выбрали удачный пример для обучения на этом этапе.

В книге Питоньяка этот пример приводится "как есть" и непонятно, зачем там два вложенных нумератора. Попробуем вместе разобраться на предыдущем примере от Михаила.
Первый нумератор разбивает текст ячейки на параграфы.
oParEnum = oCell.getText.createEnumeration()
Подозреваю, что конструкцию getText. можно опустить. В примере один параграф, но их может быть и несколько.
Второй нумератор разбивает параграф на "порции". Работа с порциями описана здесь. Однако, MRI говорит, что возвращаемый объект oPortion не поддерживает сервис TextPortion. Какие именно из описанных свойств TextPortion поддерживает oPortion проще всего выяснить у MRI (Михаил может точно узнать, посмотрев в текст программы). На вкладке "Properties" мы видим интересующие нас свойства TextPortionType и TextField.
Владимир.

Борис_С

Посылаю тестовый пример - t1.ods. Внутри примера - макрос GetLinksInCell
А это код на С#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Diagnostics;

using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.uno;
using unoidl.com.sun.star.bridge;
using unoidl.com.sun.star.frame;
namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                unoidl.com.sun.star.uno.XComponentContext localContext =
                    uno.util.Bootstrap.bootstrap();
                unoidl.com.sun.star.lang.XMultiServiceFactory multiServiceFactory =
                    (unoidl.com.sun.star.lang.XMultiServiceFactory)localContext.getServiceManager();
                XComponentLoader componentLoader = (XComponentLoader)
                    multiServiceFactory.createInstance("com.sun.star.frame.Desktop");
                string fileName = "c:\\Users\\sbe.CSOFT-SPB\\Documents\\OO\\t1.ods";
                fileName = @"file:///" + fileName.Replace('\\', '/');
                XComponent xComponent = componentLoader.loadComponentFromURL(
                    fileName, "_blank",
                    0, new unoidl.com.sun.star.beans.PropertyValue[0]);
                if (fileName.ToLower().EndsWith(".ods"))
                {
                    unoidl.com.sun.star.sheet.XSpreadsheetDocument mxDocument =
                        (unoidl.com.sun.star.sheet.XSpreadsheetDocument)xComponent;
                    unoidl.com.sun.star.sheet.XSpreadsheets xSheets =  mxDocument.getSheets();
                    unoidl.com.sun.star.container.XIndexAccess xSheetsIA =
                        (unoidl.com.sun.star.container.XIndexAccess)xSheets;
                    unoidl.com.sun.star.sheet.XSpreadsheet xSheet =
                        (unoidl.com.sun.star.sheet.XSpreadsheet)
                        xSheetsIA.getByIndex(0).Value;
                    unoidl.com.sun.star.table.XCell oCell = xSheet.getCellByPosition(0, 0);

                    // получение гиперссылок в ячейке
                    unoidl.com.sun.star.text.XTextRange xCellTR =
                        (unoidl.com.sun.star.text.XTextRange)xCell;
                    unoidl.com.sun.star.container.XEnumerationAccess gText =
                        (unoidl.com.sun.star.container.XEnumerationAccess)xCellTR.getText();
                    unoidl.com.sun.star.container.XEnumeration oParEnum = gText.createEnumeration();
                    if (xCell.getType() == unoidl.com.sun.star.table.CellContentType.TEXT)
                    {
                        while (oParEnum.hasMoreElements())
                        {
                            uno.Any oElement = oParEnum.nextElement();
                            unoidl.com.sun.star.text.XTextRange xTextRange =
                                (unoidl.com.sun.star.text.XTextRange)oElement.Value;
                            unoidl.com.sun.star.beans.XPropertySet xPropSet =
                                (unoidl.com.sun.star.beans.XPropertySet)xTextRange;
                            String sTextPortionType =
                                (String)xPropSet.getPropertyValue("TextPortionType").Value;
                        }
                    }
                }
                xComponent.dispose();
            }
            catch (System.Exception e)
            {
                string message = String.Format("Ошибка.\n{0}\n{1}",
                    e.Message, e.StackTrace.TrimStart());
                Debug.WriteLine(message);
            }
        }
    }
}


На операторе

String sTextPortionType =
                                (String)xPropSet.getPropertyValue("TextPortionType").Value;

возникает исключение.

Борис_С

Цитата: sokol92 от  3 декабря 2021, 15:04Борис, Вы вряд ли выбрали удачный пример для обучения на этом этапе.
Это пример не для обучения. Он нужен для работы.

sokol92

Сразу вопрос: где второй CreateEnumeration?
Владимир.

Борис_С

Он не нужен. Все работает и с одним (во всяком случае в моем тестовом примере точно).

mikekaganski

Цитата: sokol92 от  3 декабря 2021, 15:04Подозреваю, что конструкцию getText. можно опустить.

Да. В данном случае getText() возвращает саму ячейку.

Цитата: sokol92 от  3 декабря 2021, 15:04В примере один параграф, но их может быть и несколько.

В общем случае да. В случае Calc - нет. Ячейка не может содержать несколько параграфов (по крайней мере пока). Но API реализован однотипно - объекты "ячейка" являются коллекцией параграфов (пусть даже в коллекции он только один), а те в свою очередь - коллекцией текстовых порций (последовательностей символов с одинаковыми свойствами). Одинаково что для Calc, что для таблиц Writer.
С уважением,
Михаил Каганский

mikekaganski

Цитата: sokol92 от  3 декабря 2021, 15:23
Сразу вопрос: где второй CreateEnumeration?
Цитата: Борис_С от  3 декабря 2021, 15:25
Он не нужен. Все работает и с одним (во всяком случае в моем тестовом примере точно).

Код из ответа #49:


' Получение информации о гиперссылках в ячейке
Sub GetLinksInCell(row, col)
Dim oSheet, oCell, nItems, nPos, i, selText, oCursor, nRef
    oSheet = thisComponent.CurrentSelection.getSpreadsheet   
oCell = oSheet.GetCellByPosition(row, col)
If oCell.Type = com.sun.star.table.CellContentType.TEXT Then ' Содержимое ячейки - текст
  Dim oParEnum, oParElement, oElement, oEnum, location
  oParEnum = oCell.getText().createEnumeration()
  Do While oParEnum.hasMoreElements ()
    oParElement = oParEnum.nextElement()
    oEnum = oParElement.createEnumeration()
    Do While oEnum.hasMoreElements ()
      oElement = oEnum.nextElement()
      If oElement.TextPortionType = "TextField" Then
        If oElement.TextField.supportsService("com.sun.star.text.TextField.URL") Then
            Print "Текст  гиперссылки: " & oElement.TextField.Representation
          EndIf
      EndIf
        Loop
     Loop
   EndIf
End Sub


И там таки два CreateEnumeration.
С уважением,
Михаил Каганский

sokol92

Борис, прочитайте, пожалуйста, ответ #48 - я старался...
Владимир.

Борис_С

Обязательно. Это действительно моя ошибка. Сейчас исправлю.

sokol92

Цитата: mikekaganski от  3 декабря 2021, 15:26В общем случае да. В случае Calc - нет.

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

mikekaganski

Очень интересно! С точки зрения итератора это действительно как будто разные параграфы (хотя фактически это текст, разбитый разрывами строк). С другой стороны, почему бы и нет - может быть, даже можно программно задать разные свойства на уровне параграфов? Очень интересно - спасибо, Владимир!
С уважением,
Михаил Каганский

Борис_С

Ура! Теперь все работает. Володя, большое спасибо.