Прочитать текстовый файл в кодировке UTF-8 BOM

Автор timal1234, 14 ноября 2025, 19:52

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

economist

Дальше ещё больше вариантов. Есть некая уверенность что лучше (быстрее, но главное удобнее) чем Python и Basic окажется использование штатного функционала Ctrl+Shift+F4 с:

1) с text driver Base (SQL там беден :(

2) с DuckDB, это самый быстрый в мире и очень мощный OLAP-движок с ленивым чтением/связыванием (как MS Access) с текстовыми файлами.

Это лучше чем Basic-функция, потому что будет работать в Writer, Calc, Impress, Draw и не потребует программирования. При этом дополнить Фильтр условием в Select ... Where ... или интерактивно задать - сможет любой юзер, SQL даётся людям легко.

То есть наш спортивный интерес - вдруг да перейдет в практическую востребованность кем то, кто Alt+F11 не любит?
Пить не буду коньяка - читану Питоньяка!

sokol92

Итак, двигаемся дальше.

Можно дополнительно уменьшить время работы макроса из файла TestFileToSheet2.ods, если прочитать весь файл в переменную, получить массив строк с помощью функции Split и далее фильтровать уже эти строки. Но тут есть серьезные подводные камни, поскольку могут возникнуть проблемы с нехваткой оперативной памяти для файлов очень большого размера.
Написать такой макрос предлагается в качестве упражнения.
Время выполнения (у меня) - 11 сек.

Следующий шаг для повышения быстродействия мы можем сделать, если вспомним, что LibreOffice поддерживает Python в качестве языка программирования (о чем регулярно напоминает коллега @economist).

Реализуем тот же алгоритм (прочитать построчно текстовый файл в кодировке UTF-8, отфильтровать по контексту и занести на лист электронной таблицы).
Текст не будет длинным:
import uno


def file_to_sheet(path_uri, text_to_find, sheet):
    with open(uno.fileUrlToSystemPath(path_uri), "r", encoding="utf-8") as file:
        data_array = [(line.strip(),) for line in file if text_to_find in line.lower()]

    sheet.getCellRangeByPosition(0, 0, 0, len(data_array) - 1).setDataArray(data_array)
    return len(data_array)

Теперь текст функции на Basic:
' Читает текстовый файл fileURL в кодировке UTF-8 и записывает строки, содержащие textToFind,
' в первый столбец листа oSheet.
' Возвращает число записанных строк.
Function FileToSheet(Byval fileURL as String, Byval textToFind as String, Byval oSheet as Object) as Long
   Dim pyScript As Object, retval
   pyScript = PyGetDocScript(ThisComponent, "Module.py", "file_to_sheet")
   retval = pyScript.invoke(Array(fileURL, textToFind, oSheet), Array(), Array())
   FileToSheet = retval
End Function


где вспомогательная функция определена так:
' lang:ru
' Возврашает объект для сценария Python документа (Nothing при ошибке).
Function PyGetDocScript(Byval oDoc as Object, ByVal module As String, ByVal script As String) As Object
  On Error GoTo ErrLabel
  PyGetDocScript=oDoc.getScriptProvider().getScript("vnd.sun.star.script:" & _
                 module & "$" & script & "?language=Python&location=document")
  Exit Function
 
ErrLabel:                 
  PyGetDocScript = Nothing
End Function



Время выполнения (у меня) - 1 сек (1010 мс).
За счет чего достигнуто такое значительное ускорение? Благодаря исключительной эффективности интерпретатора языка Python.

Исходная задача практически решена.

Теоретически можно двигаться дальше. Если в функции Python закомментировать вызов метода setDataArray, то время выполнения у меня составляет 929 мс (то есть разработчики LO переносят массив на лист исключительно эффективно). Кстати, вполне возможно, что в следующих версиях Python код (или его модификация) будет выполняться еще быстрей.

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

Владимир.

economist

Штош, @sokol92, 1:1, отличный пример с python-way стилем (менеджер контекста, кортеж-включения). Вызов принят, но смогу заняться после 15/12 (аврал).

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

Так что с добавлением этих функций к любому нашему способу - время вырастет, и, уверен,  Pandas со своей индексацией, срезами и сахаром .query() и .eval() и df.convert_dtypes() будет проще в доработке, и выстрелит в "яблочко", дав очень удобный инструмент.

А если мы тут договоримся и набросаем свой Domain Language для "языка запросов", скажем, для фильтров загружаемых строк в первой строке Листа - то может получится то, что пригодится вообще всем.

Хотя повторюсь, Ctrl+Shift+F4 шедеврален по задумке авторов и может с SQL зайти миллионам. Просто о нем никто не знает, а слетающая ширина столбов подбешивает на узких мониторах. В общем, я в игре!
Пить не буду коньяка - читану Питоньяка!

economist

#18
В деле выдумывания своего мета-языка импорта - очень полезны "ранние соглашения". Как бы хитро и длинно не назывались колонки в txt, почти в любой таблице найдется глазами троица: колво, цена, сумма. Их связь очевидна, как и их допустимый тип. Глаз так же быстро найдет едизм, наим, фио, адрес, nпп и примеч. А раз может глаз - сможет и Python.

И очень, наверно, будет удобно загружать данные - не все подряд, а просто перечислив эти короткие слова в нужном порядке (в первой строке). Питон умеет в NLP и легко сопоставит зоопарк имён с их алиасами., невзирая на падежи,  мн.ч. и род существительных. Короче он сумеет в начальную форму слова привести все колонки. Главное придумать каким мета словом удачно всё обозначить. Слово "колво" - отличное, легкое в наборе.

И можно фантазировать дальше. Например укажем несуществующий столбец сумма$ - и Питон стукнется на cbr.ru, возьмёт тек курс доллара и сам налёту вставит сумму в валюте. Что-то в этом подходе есть...
Пить не буду коньяка - читану Питоньяка!

economist

#19
Продолжаем. А что "из коробки"? А вот что: Calc перетащенный в него SQL-запрос запоминает как "диапазон". Простейший SQL-запрос
SELECT Поле from testfile Where Поле like '%Обучение%'
возвращает из 1 млн строк 10k... тоже за 1 секунду (AMD 7840).
Без Basic, Python, Pandas и вообще без программирования.

При этом изменить SELECT-запрос легко по ПКМ-Редактировать-Запросы-SQL...
И у вас по Ctrl+Shift+F4 - единый интерфейс ко всем приложениям Либры и ко всем данным мира: 1С, web, десяткам SQL базам данных (FireBird, SQLite, MySQL, PostgreSQL, DuckDB, ClickHouse итд). Из этого же окна заполняются удобными кнопками Данные в Текст CalcДанные в Текст/Поля Writer и Слияние в Writer (та самая мега-рутина при заполнении Заявлений, Договоров итп).

Во вложении ods и odb файл. Я добавил в код макроса CreateTestFile вставку строки заголовка (слово Поле). Не забудьте запустить разок макросы CreateTestFile и AutoRegDB для регистрации ODB-файла в LibreOffice (для Ctrl+Shift+F4 и авто-обновы) и переоткрыть TestFileToSheet5.ods

Он спросит: Этот файл содержит запросы, результаты которых не были сохранены.
Повторить эти запросы? - Ок
- и через 1 сек покажет 10 тыс строк. Запрос можно кешировать, сняв флаг в Данные - Задать - Параметры - Не сохранять...



Не знаю как у вас коллеги, а вот мне каждый раз стыдно перед разработчиками LibreOffice, когда я вместо использования готового штатного функционала лезу кодить "одноразовые" макросы, наплевав на их труд.
 
Вот если бы мы еще создали мини-язык отбора. В нем нужно всего-то договориться о том как сложные вещи называть просто. Плохих примеров много, например так в dBase кодировали числовые поля вот так: СУММА,N,30 Это точно избыточно: мы и так знаем что сумма это длинное число.

Ctrl+Shift+F4.jpg

Пить не буду коньяка - читану Питоньяка!