Пакетная замена с использованием регулярных выражений

Автор NewUser, 3 июля 2021, 05:14

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

NewUser

Здравствуйте. Из документа pdf получен большой текст, в котором, к сожалению, утрачены нижние индексы - они превратились в обычные цифры. Возможна ли в LO Writer пакетная замена цифр 0-9 на соответствующие им нижние индексы ₀₋₉? Возможно ли составить в LO Writer регулярное выражение для замены цифр [0-9], встречающихся после латиницы [A-z] один или два раза {1,2} на [₀₋₉]? Спасибо.

eeigor

#1
Отвечаю без компьютера
Word позволяет как вести поиск с учётом формата, так и производить замену с изменением оного. Вероятно, Writer тоже.
То есть ищем (?<=[A-Za-z])\d{1,2} и меняем само на себя с установкой нижнего индекса. Это реально?
Если нет, то макросом (позже, если не помогут другие)
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

economist

#2
Если говорить о серийном решении, то перебрать сотни страниц диссера и назначить super-/subscript символам (в терминах LO Writer - Верхний/Нижний индекс) и только там где можно/нужно - может Python-скрипт. Какой индекс ставить - скрипт может решать сам:
- Для символьной алгебры со степенями - нужна библиотека Sympy (она даже умеет решать уравнения)
- Для химии с числами, показывающими число молекул - другие либы (они есть всегда на pypi.org) и все они свободны.

Найденные в тексте формулы скрипт может заменять или "вычислять" выделенный текст по нажатию клавиш (кмк так правильнее, с вычиткой).  

Либы не дадут пролезть ошибками в формулах, не дадут создать несуществующее вещество или бессмыслицу. Впрочем число "формул" в текстах всегда конечно, поэтому текст макроса будет состоять из валидных формул строк присваивания вида


2^8 = '2<sup>8</sup>                  # 2 в степени 8, регулярки тут бессильны'
CaCO3 = 'CaCO<sub>3</sub>'            # кальция карбонат
C2H6O = 'C<sub>2</sub>H<sub>6</sub>O' # просто "он"


Для форматирования макросом числовых индексов - https://www.openoffice.org/api/docs/common/ref/com/sun/star/style/CharacterProperties.html#CharEscapement - API-метод "CharEscapement", примеры форматирования выкладывали тут https://forumooo.ru/index.php/topic,2430.0.html
Руб. за сто, что Питоньяк
Любит водку и коньяк!
Потому что мне, без оных, -
Не понять его никак...

mikekaganski

#3
Цитата: NewUser от  3 июля 2021, 05:14
Возможна ли в LO Writer пакетная замена цифр 0-9 на соответствующие им нижние индексы ₀₋₉? Возможно ли составить в LO Writer регулярное выражение для замены цифр [0-9], встречающихся после латиницы [A-z] один или два раза {1,2} на [₀₋₉]?

Отвечу наконец на заданный вопрос (то есть о замене символов U+0030-U+0039 на символы U+2080-U+2089, а не о применении форматирования или об использовании чего-то третьего).

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

(?<=[A-Za-z]|[A-Za-z][0-9₀-₉])0

на отдельный символ Эта регулярка не проверяет, что после буквы идёт не больше 2 цифр - поэтому заменит C123 на C₁₂3. Если нужен контроль - придётся делать двадцать замен: сначала десять

(?<=[A-Za-z])0(?![0-9]{2})

а затем десять

(?<=[A-Za-z][₀-₉])0(?![0-9])

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

Макросом было бы проще.


sub replaceNumbersWithSubscripts
  dim doc as object, undoManager as object, desc as object, i as integer
  doc = thisComponent
  desc = doc.createSearchDescriptor()
  desc.SearchRegularExpression = True
  undoManager = doc.getUndoManager()
  doc.lockControllers()
  undoManager.enterUndoContext("Make numbers subscript")
  on error goto cleanup
  for i = 0 to 9
    desc.setSearchString("((?<=[A-Za-z])" & i & "(?![0-9]{2}))|((?<=[A-Za-z][0-9₀-₉])" & i & "(?![0-9]))")
    desc.setReplaceString(Chr(&H2080 + i))
    doc.replaceAll(desc)
  next i
cleanup:
  undoManager.leaveUndoContext
  doc.unlockControllers()
end sub
С уважением,
Михаил Каганский

sokol92

Добрый день, Михаил! Спасибо за enterUndoContext, до этого не попадался на глаза.  :)
В Excel VBA нет аналогов этой конструкции.
Владимир.

mikekaganski

Цитата: sokol92 от  3 июля 2021, 16:28В Excel VBA нет аналогов этой конструкции.

Похоже, там всё-таки часть понимала, что это бывает полезно: https://docs.microsoft.com/en-us/office/vba/api/Project.Application.OpenUndoTransaction
С уважением,
Михаил Каганский

sokol92

#6
Понятие "unit of work" для обозначения множества согласованных изменений, которые можно принять или откатить одновременно - очень старое и существовало (по крайней мере, в продуктах IBM) давно, еще до великого открытия SQL.
Продукт Project это уже "новодел".  :)
Владимир.

NewUser

Уважаемые форумчане! Большое спасибо за ваши ответы.

eeigor

#8
Добрался до компьютера...
Цитата: eeigor от  3 июля 2021, 07:13Word позволяет как вести поиск с учётом формата, так и производить замену с изменением оного. Вероятно, Writer тоже.
То есть ищем (?<=[A-Za-z])\d{1,2} и меняем само на себя с установкой нижнего индекса. Это реально?
Да, это реально, как и предполагалось изначально. Writer'а не знаю, но иду "по аналогии" с Word'ом...

Цитата: NewUser от  3 июля 2021, 05:14Возможно ли составить в LO Writer регулярное выражение для замены цифр [0-9], встречающихся после латиницы [A-z] один или два раза {1,2} на [₀₋₉]?
Найти: (?<=[A-Za-z])(\d{1,2})
Заменить: $1
Формат: нижний индекс (Subscript) на вкладке "Положение" (Position). См. скриншот 4 (2).

Итог: замена формата производится разом ("Заменить все"), то есть "пакетом", как вы и просили.
Смысл действия: уменьшается размер шрифта найденного текста и этот текст опускается под опорную линию на заданную величину в % (масштаб шрифта, смещать на).

В данном вопросе LO Writer не уступает MS Word (и даже превосходит его: там поддержка регулярных выражений весьма "куцая", хотя и есть, если кто не знал).
Примечание. В дополнение к моему первому регулярному выражению я только взял "цифры" в скобки ("захват", выше выделил красным), что и соответствует выражению $1 в поле "Заменить" (то есть тому, что будет захвачено).

Макрос не требуется. Но написать его несложно.
Sub Main
Dim aSearchStrings$()
aSearchStrings = Array("(?<=[A-Za-z])(\d{1,2})")
Call changeList(aSearchStrings)
End Sub

Sub changeList(mList As Variant)
Dim n&
Dim oDocument As Object
Dim oSearch As Object, oFound As Object
Dim oFoundCursor As Object

oDocument = ThisComponent
oSearch = oDocument.createSearchDescriptor
oSearch.SearchRegularExpression = TRUE
For n = LBound(mList()) To UBound(mList())
oSearch.SearchString = mList(n)
oFound = oDocument.findFirst(oSearch)
While NOT IsNull(oFound)
oFoundCursor = oFound.Text.createTextCursorByRange(oFound)

REM Здесь может быть что угодно, но в данном случае то, что вам нужно:
oFoundCursor.CharEscapement = -33  'how much to lower (subscript value)
oFoundCursor.CharEscapementHeight = 58  'percentage height
REM **********************************************************************
oFound = oDocument.findNext(oFound, oSearch)
Wend
Next n
End Sub


Примечание. Положением и размером символов управляют два свойства: CharEscapement и CharEscapementHeight. Их значения в коде соответствуют значениям на скриншоте.
Решение выше - типовое. Выполняется обход списка замен. Вы можете отредактировать регулярное выражение или добавить сколь угодно много других вариантов через запятую в массив поиска:
aSearchStrings = Array("(?<=[A-Za-z])(\d{1,2})")
Требуемые действия можно передать с параметрами вызываемой процедуры. В примере выше - запрограммировано жёстко.

UPD:
Судя по всему, макрос дублирует действия пользователя через диалог "Найти и заменить".
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

eeigor

#9
Цитата: mikekaganski от  3 июля 2021, 14:48Отвечу наконец на заданный вопрос (то есть о замене символов U+0030-U+0039 на символы U+2080-U+2089, а не о применении форматирования или об использовании чего-то третьего).
Михаил, полагаю, что в контексте вопроса автора темы предпочтительно вести речь именно о форматировании, а не о замене одних символов на другие. После замены символов текст становится уже "другим". Более того, символы юникода неудобно вводить с клавиатуры.

UPD:
Решение задачи с заменой символов привело к усложнению регулярного выражения.

"((?<=[A-Za-z])" & i & "(?![0-9]{2}))|((?<=[A-Za-z][0-9₀-₉])" & i & "(?![0-9]))"

"(?<=[A-Za-z])(\d{1,2})"  'в макросе скобки захвата можно убрать
"(?<=[A-Za-z])\d{1,2}"
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

NewUser

#10
Я предпочитаю не пользоваться форматированием, а заменять по возможности индексы на верхние и нижние символы. Я благодарен всем авторам ответов, но мне нужно время, чтобы разобраться, поскольку поиск и замена в LO Writer для меня всегда были проблемой. Первый макрос, предложенный ув. mikekaganski, отработал на отлично. Если позволите, я расширил бы свой вопрос на возможность замены после латиницы не только цифр, но и знаков +− (минус)=() на соответствующие подстрочные символы. Спасибо.

eeigor

#11
Цитата: NewUser от  4 июля 2021, 01:46Я предпочитаю не пользоваться форматированием, а заменять по возможности индексы на верхние и нижние символы.
Если позволите, я расширил бы свой вопрос на возможность замены после латиницы не только цифр, но и знаков +− (минус)=() на соответствующие подстрочные символы.

Дилемма: дать пользователю то, что он просит, или то, что нужно?

В моем решении (#8) уже содержится ответ на ваш вопрос со всеми "вариациями": сместить можно что угодно. Если вверх, то в диалоговом окне выберите соответствующую опцию, а в макросе присвойте значение со знаком плюс (вниз - со знаком минус, как в моём примере кода ниже).
oFoundCursor.CharEscapement = -33  'how much to lower (subscript value)

Поскольку знак + является квантификатором, то его надо экранировать:
\+
(и круглые скобки тоже) и вставить в регулярное выражение в нужное место, или добавить – в макросе – ещё одну "регулярную строку" в список замен.
aSearchStrings = Array("(?<=[A-Za-z])(\d{1,2})")

Вы можете самостоятельно управлять положением смещаемого текста и масштабом его отображения (высотой символов). При этом текст может быть абсолютно любой, а не только имеющий «индексный» аналог в юникоде. Почувствуйте разницу! Более того, сохраняется возможность поиска этого текста при вводе с клавиатуры, поскольку текст не изменён: что видим, то и ищем.
Моё решение перекрывает все ваши запросы, выглядит просто и является стандартным.

@mikekaganski дал вам в точности то, что вы попросили, но это частный случай, а я предложил более общее и универсальное решение, которое сделает индексом – верхним/нижним – любой символ и не нарушит взаимодействия пользователя с клавиатурой (что вижу, то и могу найти).
Расширяя свой вопрос, вы должны понимать, что решение «с заменой на индексы» существует, если в юникоде соответствующие символы представлены как верхние/нижние индексы. Там много чего есть... Но на счёт всех знаков я не уверен, однако*.
Ваше предпочтение не пользоваться форматированием мне непонятно... Я уже упоминал, что не работаю с этим редактором. Он, что, так плох, что не держит формат? Поделитесь своими соображениями и наблюдениями, если не сложно.

Ещё раз замечу, что предложенное мной решение позволяет ограничиться диалогом «Найти и заменить». Макрос есть, но не требуется. Юникод не используется. Замена – «пакетная». Регулярное выражение легко читается. Доступно всем.

P.S. Если мой подход вам интересен, то я могу составить регулярное выражение. Уточните условия. Дело в том, что в выражении выше цифры-индексы следуют за латинской буквой A-z, как вы сказали, а не после знаков плюс/минус или равенства, или открывающей скобки. Вы можете это сделать и сами.
Если просто добавить эти знаки в указанный массив, то получится что-то вроде:
H2O(1+3=4)
А что надо?
H2O(1+3=4)

Полагаю, что "опустить" нужную скобку не так просто: ведь скобок в тексте немерено, и не все "индексные".
Но диалог "Найти и заменить" позволяет заменить только нужное через действия "Следующее" и "Заменить"... под вашим пристальным вниманием. :)


* UPD 1: Unicode

₊ Нижний индекс плюс U+208A (⁺ Верхний индекс плюс U+207A)
₋ Нижний индекс минус U+208B (⁻ Верхний индекс минус U+207B)
₌ Нижний индекс равно U+208C (⁼ Верхний индекс равно U+207C)
₍ Нижний индекс круглая скобка U+208D (⁽ Верхний индекс левая скобка U+207D)
₎ Нижний индекс круглая скобка U+208E (⁾ Верхний индекс правая скобка U+207E)


UPD 2: Решение с заменой на юникод выглядит надёжным (в плане "не сломать" форматированием), но отдаёт мазохизмом, на мой взгляд... Всё имеет право на существование.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

NewUser

Отлично, пока для меня достаточно информации. Буду разбираться, практиковаться. Ещё раз спасибо всем ответившим.