Сквозная нумерация документов

Автор JohnSUN, 28 марта 2013, 17:55

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

JohnSUN

Slava Polonsky задал на странице сообщества интересный вопрос.
Речь опять идёт о сквозной нумерации создаваемых документов.
Надеюсь, он вот-вот присоединиться к нашей компании и ответит на несколько уточняющих вопросов
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

JohnSUN

В общем, заготовка макроса пока выглядит так:
Sub OnNewWorkbook(oEvent)
Const dictTable="C:\NumDocs\NumbersOfDocuments.ods"
Dim tmpltName$ ' Имя шаблона текущего (только что созданного) документа
Dim targetPath$ ' Где сохранить
Dim targetName$ ' С каким именем
Dim oDoc, tDoc ' Текущий документ и книга-справочник, которая лежит в dictTable
Dim bDisposable As Boolean ' Признак того, что словарь уже был открыт
Dim aTmplts ' Таблица всех нумеруемых шаблонов с первого листа книги tDoc
Dim oSheets, oSheet, oRange, oCell ' Коллекция листов книги, один лист, диапазон ячеек, одна ячейка
Dim i&, j&, num& ' Просто переменные для организации циклов или временного хранения каких-то значений
Dim argOpenFile(0) as new com.sun.star.beans.PropertyValue ' Параметры открытия справочника: скрытый
argOpenFile(0).Name = "Hidden"
argOpenFile(0).Value = True

print oEvent.EventName+" "+ oEvent.Source.Identifier+" "+ oEvent.Source.DocumentProperties.TemplateName
REM Обрабатываются только электронные книги - другие типы документов пока пропускаем
If oEvent.Source.Identifier <> "com.sun.star.sheet.SpreadsheetDocument" Then Exit Sub
If NOT FileExists(dictTable) Then
MsgBox("Где-то потерялся файл '"+dictTable+"'!", 48, "Ой, беда!")
Exit Sub
EndIf
REM Имя шаблона, на основании которого создан новый документ:
tmpltName = oEvent.Source.DocumentProperties.TemplateName
REM Несколько процедур уже написаны и лежат в стандартной библиотеке Tools
REM Загрузим её:
GlobalScope.BasicLibraries.LoadLibrary("Tools")
tDoc = OpenDocument(ConvertToURL(dictTable), argOpenFile(), bDisposable)
If IsNull(tDoc) or IsEmpty(tDoc) Then
MsgBox("Не удалось открыть файл '"+dictTable+"' - возможно он кем-то занят", 48, "Упс!")
Exit Sub
EndIf
oSheets = tDoc.getSheets()
oSheet = oSheets.getByIndex(0) ' В словаре нас интересует только первая страница
oRange = oSheet.getCellRangeByPosition(0, 0, 6, GetLastUsedRow(oSheet)) ' С А1 по колонку G до последней заполненной строки
aTmplts = oRange.getDataArray()
j = -1
For i = LBound(aTmplts) To UBound(aTmplts)
If aTmplts(i)(0) = tmpltName Then
j = i
targetPath = aTmplts(i)(1)
targetName = aTmplts(i)(2)
Exit For
EndIf
Next i
if j = -1 Then Exit Sub ' Шаблон этой книги нумерации не подлежит
... ну, и так далее
End Sub

Нужно уточнить относится ли вся эта история только к книгам Calc. Или другие документы (текстовые, например) тоже должны попасть в эту схему?
(Сейчас, к сожалению, должен не надолго отвлечься...)
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

JohnSUN

#2
Ну, в общем, что-то в этом роде.

Содержимое архива NumberDocs.zip импортируем в Мои макросы.
Архив NumDocs.zip распаковываем в корень диска C: (там папка с образцом словаря)
На событие офиса цепляем макрос...
Создаем шаблоны, сохраняем их. Вносим нужные строки в словарь...

PS. Ах да! Образец шаблона забыл приложить...

[вложение удалено Администратором]
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

JohnSUN

"Продолжаем разговор..." (с) Карлсон
Разумеется, последняя исправленная ошибка в коде оказалась - как всегда! - только предпоследней... Перечитал код свежими глазами и отловил несколько недочетов и помарок: использование индекса i вместо j, неправильная последовательность обработки ошибки записи нового файла и еще кое-чего по мелочи... В общем, код похожий на альфа-версию выглядит так:
Option Explicit
Option Base 0   

Sub OnNewWorkbook(oEvent)
Const dictTable="C:\NumDocs\NumbersOfDocuments.ods"
Dim tmpltName$ ' Имя шаблона текущего (только что созданного) документа
Dim targetPath$ ' Где сохранить
Dim targetName$ ' С каким именем
Dim oDoc, tDoc ' Текущий документ и книга-справочник, которая лежит в dictTable
Dim bDisposable As Boolean ' Признак того, что справочник уже был открыт
Dim aTmplts ' Таблица всех нумеруемых шаблонов с первого листа книги tDoc
Dim tSheets, oSheets ' Коллекции листов книги-справочника и новой книги
Dim tSheet, oSheet ' Oдин лист со справочником и лист, куда будем вписывать новый номер
Dim tRange ' Диапазон ячеек, с содержимым справочника
Dim tCell, oCell ' Ячейка в справочнике (если нужно ткнуть в ошибку в данных) и ячейка, куда будем вписывать новый номер
Dim i&, j&, num& ' Просто переменные для организации циклов или временного хранения каких-то значений
Dim argOpenFile(0) as new com.sun.star.beans.PropertyValue ' Параметры открытия справочника: скрытый
argOpenFile(0).Name = "Hidden"
argOpenFile(0).Value = True
oDoc = ThisComponent ' Это просто - кто вызвал макрос, тот и есть новый документ
REM Обрабатываются только электронные книги - другие типы документов пока пропускаем
If oEvent.Source.Identifier <> "com.sun.star.sheet.SpreadsheetDocument" Then Exit Sub
If NOT FileExists(dictTable) Then
MsgBox("Где-то потерялся файл '"+dictTable+"'!", 48, "Ой, беда!")
Exit Sub
EndIf
REM Имя шаблона, на основании которого создан новый документ:
tmpltName = oEvent.Source.DocumentProperties.TemplateName
REM Несколько процедур уже написаны и лежат в стандартной библиотеке Tools. Загрузим её:
GlobalScope.BasicLibraries.LoadLibrary("Tools")
REM Загрузим книгу-справочник
tDoc = OpenDocument(ConvertToURL(dictTable), argOpenFile(), bDisposable)
If IsNull(tDoc) or IsEmpty(tDoc) Then
MsgBox("Не удалось открыть файл '"+dictTable+"' - возможно он кем-то занят", 48, "Упс!")
Exit Sub
EndIf
tSheets = tDoc.getSheets()
tSheet = tSheets.getByIndex(0) ' В справочнике нас интересует только первая страница
REM А на ней данные с А1 по колонку G до последней заполненной строки
tRange = tSheet.getCellRangeByPosition(0, 0, 6, GetLastUsedRow(tSheet))
aTmplts = tRange.getDataArray()
j = -1
REM Перебираем список со второй строки (в первой у нас заголовки, не нужны)
REM в надежде, что имя текущего шаблона в справочнике есть
For i = LBound(aTmplts)+1 To UBound(aTmplts)
If aTmplts(i)(0) = tmpltName Then ' Сравниваем с точностью до регистра символов.
j = i
Exit For
EndIf
Next i
If j = -1 Then ' Шаблон этой книги нумерации не подлежит, его нет в справочнике
If bDisposable Then DisposeDocument(tDoc)
Exit Sub
EndIf
targetPath = aTmplts(j)(1)
targetName = aTmplts(j)(2)
oSheets = oDoc.getSheets()
If (oSheets.getCount()-1 < aTmplts(j)(3)) OR (aTmplts(j)(3)<0) Then
MsgBox("В шаблоне этой книги нет листа с номером "+(aTmplts(j)(3))+" - исправь справочник", 48, "Чиво?!!")
tCell = oSheet.getCellByPosition(3, j)
tDoc.getCurrentController().select(tCell)
tDoc.getCurrentController().getFrame().getContainerWindow().setVisible(True)
DisposeDocument(oDoc) ' Поскольку присвоить номер этому документу не можем, то и не сохраняем его
Exit Sub
EndIf
num = aTmplts(j)(6)+1 ' Увеличили старый номер на единичку
oSheet = oSheets.getByIndex(aTmplts(j)(3))
oCell = oSheet.getCellByPosition(aTmplts(j)(5), aTmplts(j)(4))
oCell.setValue(num) ' Новый номер вписан на свое место. Попробуем сохранить документ
If InStr(targetName, "#") > 0 Then ' Будем позицию номера в имени файла обозначать "решеткой"
targetName = Join(Split(targetName, "#"), Trim(Str(num)))
Else ' В шаблоне имени не предусмотрено место для номера. Будет "номер"_"имя документа"
targetName = Trim(Str(num)) + "_" + targetName
EndIf
If Right(targetPath,1) <> GetPathSeparator() Then targetPath = targetPath + GetPathSeparator()
On Error GoTo NoWriteFile
oDoc.StoreAsURL(ConvertToURL(targetPath+targetName), Array()) ' Возможно вместо Array() потребуется массив каких-то параметров
GoTo NormalContinue
NoWriteFile:
DisposeDocument(oDoc)
MsgBox("Не удалось сохранить документ '"+targetPath+targetName+"' - устрани причину ошибки и попробуй еще раз", 48, "Блин!!!")
GoTo EndOfWork
NormalContinue:
REM Если при сохранении не возникло ошибки, можем перезаписать справочник
On Error GoTo 0
aTmplts(j)(6) = num
tRange.setDataArray(aTmplts)
tDoc.store()
EndOfWork:
If bDisposable Then tDoc.close(True)
End Sub

Если не спешить с созданием очередного файла - работает вроде бы устойчиво... Если успеть создать несколько документов из одного шаблона (восемь раз кликнуть на .ots, например) - заткнется на ошибке...
Коллеги, тут одна проблемка нарисовалась. У Polonsky какой-то из Линуксов, а я в константу dictTable Виндовый путь прописал. Поможете ему исправить эту строчку и пути в файле NumbersOfDocuments (у меня в тех осях навыков маловато)?
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

...

#4
Цитировать"Архив NumDocs.zip распаковываем в корень диска C: (там папка с образцом словаря)"

Его же можно куда угодно распаковать. В Linux для простоты можно распаковать в домашний каталог пользователя. Путь там будет выглядеть так: "/home/Имя_Пользователя/NumbersOfDocuments.ods", где "Имя_Пользователя" - имя учетной записи. У меня она зовется dmitry и, следовательно, путь выглядит "/home/dmitry/NumbersOfDocuments.ods".

Внимание! В Unix в путях слешы  в другую сторону повернуты, не "\", а "/".

JohnSUN

#5
Спасибо за помощь, дружище... А /root/... дописывать не нужно? Типа, системе и так ясно, о каком home идет речь?

А насчет "куда распаковывать" - это для тех, кто код читать-править не стал бы... Я-то путь к словарю наглухо вписал...
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне

VlhOwn


...

#7
/root - это если сидят из аккаунта root, что является дурным тоном и обычно не принято. Поэтому точно /home. А home там одна, а в ней учетные записи всех пользователей. Там иначе чем в виндовс все устроено, нету лишних повторений.

JohnSUN

Спасибо за разъяснения, коллеги. Чую, нужно выделить время и поковыряться в какой-нибудь Убунте, освежить навыки.

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

Первый документ|Второй документ
По шаблону создался новый документ, сработало событие OnNew|
Макрос поискал книгу-справочник среди уже открытых - не нашел|
Открыли справочник с диска, запомнили, что её потом нужно закрыть|Запускаем создание еще одного документа, сработало событие OnNew
Присваиваем номер новому документу, фиксируем номер в справочнике|Поискали справочник среди уже открытых и нашли
Сохраняем новый документ в указанное место с нужным именем|Запомнили, что закрывать справочник не нужно (он ведь был открыт)
Сохраняем справочник|Считываем из справочника данные, присваиваем новый номер документу
Закрываем справочник (ведь запомнили, что нужно закрыть)|Сохраняем документ
|Пытаемся сохранить в справочнике новый номер - а справочника уже нет
Вижу как минимум три варианта обхода ошибки:
1. Никогда не закрывать справочник. Не аккуратненько как-то...
2. Взводить какой-то глобальный логический флаг при запуске макроса. При входе в макрос в цикле ждать пока не завершится обработка предыдущего OnNew и флаг не сбросится, затем взвести флаг, выполнить все нужные действия и флаг сбросить. Тогда вместо параллельного выполнения макросов получим последовательную обработку... Должно сработать. Но что-то грызут сомнения насчет глобальных переменных. Воде как не всегда они себя ведут так, как ожидалось...
3. Отключать обработку события OnNew на время работы макроса. Тоже как-то не по фен-шуй...
Ну, или смириться и просто ждать некоторое время перед созданием очередного документа. Типа, документированная особенность алгоритма ;)

Ваше мнение? Какой из способов наиболее приемлем? И все ли способы я перечислил?
Владислав Орлов aka JohnSUN
Благодарить-не зазорно.
Подарить благо создателям офиса, нашему ресурсу, мне