Jeżeli uruchamiasz OOo w trybie użytkownika i w nim zapuszczasz makropolecenie, to oczywiście środowisko graficzne jest potrzebne. Ale jeżeli działa w trybie serwera, z opcją --headless, to radzi sobie bez niego. Wtedy nie uruchamia się makr, tylko programy klienckie.
Pierwszy szkic ma postać makra w Basicu. Jest więc stosunkowo łatwy do przetestowania. Ale wymaga GUI, chyba że różnymi trickami uruchomimy makro w sesji działającej w trybie headless (nie próbowałem, ale nie widzę przeszkód).
Kod: Zaznacz cały
sub testMergeFiles()
doc = StarDesktop.loadComponentFromURL("private:factory/simpress", "_blank", 0, Array())
mergeFromURL(doc, "file:///c:/users/my_user/slides1.odp")
mergeFromURL(doc, "file:///c:/users/my_user/slides2.odp")
doc.DrawPages.remove(doc.DrawPages.getByIndex(0))
doc.storeAsURL("file:///c:/users/my_user/output.odp", Array())
doc.close(true)
end sub
sub mergeFromURL(doc1, url)
dsp = createUnoService( "com.sun.star.frame.DispatchHelper")
dt = 1000
ctr1 = doc1.getCurrentController()
frame1 = ctr1.Frame
doc2 = StarDesktop.loadComponentFromURL(url, "_blank", 0, Array())
ctr2 = doc2.getCurrentController()
frame2 = ctr2.Frame
dsp.executeDispatch(frame1, ".uno:DiaMode", "", 0, Array())
dsp.executeDispatch(frame2, ".uno:DiaMode", "", 0, Array())
for i = 0 to doc2.DrawPages.Count-1
page2 = doc2.DrawPages.getByIndex(i)
ctr2.setCurrentPage(page2)
dsp.executeDispatch(frame2, ".uno:Copy", "", 0, Array())
wait(dt)
page1 = doc1.DrawPages.getByIndex(doc1.Drawpages.Count-1)
ctr1.setCurrentPage(page1)
dsp.executeDispatch(frame1, ".uno:Paste", "", 0, Array())
wait(dt)
next i
dsp.executeDispatch(frame1, ".uno:DrawingMode", "", 0, Array())
doc2.close(false)
end sub
Słowo komentarza: uruchamiamy makro testMergeFiles, które zakłada nową prezentację, a potem po kolei dołącza do niej slajdy ze wskazanych plików. Na koniec usuwa pierwszy slajd, założony przy utworzeniu dokumentu, po czym zapisuje i zamyka całość.
Czas 1000 (1 sek.) być może da się skrócić, ale nie do zera, bo wtedy schowek "nie zdąży" otrzymać danych przed zmianą fokusu kontrolera, i nici z operacji.
Nazwy plików są zakodowane na sztywno i nie są przetwarzane w pętli tylko osobnymi wywołaniami, ale to jest tylko test demonstrujący ideę.
Drugi szkic został napisany w Pythonie. Oto on:
Kod: Zaznacz cały
# coding: utf8
import sys
import os
import time
import uno
import pyuno
def mergeFromURL(desk, dispatcher, doc1, url, dt = 1.0):
ctr1 = doc1.getCurrentController()
frame1 = ctr1.Frame
doc2 = desk.loadComponentFromURL(url, "_blank", 0, ())
ctr2 = doc2.getCurrentController()
frame2 = ctr2.Frame
dispatcher.executeDispatch(frame1, ".uno:DiaMode", "", 0, ())
dispatcher.executeDispatch(frame2, ".uno:DiaMode", "", 0, ())
for i in range(doc2.DrawPages.Count):
page2 = doc2.DrawPages.getByIndex(i)
ctr2.setCurrentPage(page2)
dispatcher.executeDispatch(frame2, ".uno:Copy", "", 0, ())
time.sleep(dt)
page1 = doc1.DrawPages.getByIndex(doc1.DrawPages.Count-1)
ctr1.setCurrentPage(page1)
dispatcher.executeDispatch(frame1, ".uno:Paste", "", 0, ())
time.sleep(dt)
dispatcher.executeDispatch(frame1, ".uno:DrawingMode", "", 0, ())
doc2.close(False)
def fileNameToUrl(fname):
if fname[0:7] != 'file://':
fname = os.path.realpath(fname)
return pyuno.systemPathToFileUrl(fname)
else:
return fname
def mergeFiles(ctx, desk, filelist, outname):
doc = desk.loadComponentFromURL("private:factory/simpress", "_blank", 0, ())
dispatcher = ctx.ServiceManager.createInstanceWithContext("com.sun.star.frame.DispatchHelper", ctx)
for fname in filelist:
mergeFromURL(desk, dispatcher, doc, fileNameToUrl(fname))
doc.DrawPages.remove(doc.DrawPages.getByIndex(0))
doc.storeAsURL(fileNameToUrl(outname), ())
doc.close(False)
def asMacro():
ctx = XSCRIPTCONTEXT.getComponentContext()
desk = XSCRIPTCONTEXT.getDesktop()
infiles = [
"c:/users/my_user/slides1.odp",
"c:/users/my_user/slides2.odp",
]
outfile = "c:/users/my_user/output.odp"
mergeFiles(ctx, desk, infiles, outfile)
def asService():
assert len(sys.argv) >= 2
localContext = uno.getComponentContext()
resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext)
ctx = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" )
desk = ctx.ServiceManager.createInstanceWithContext( "com.sun.star.frame.Desktop", ctx)
infiles = sys.argv[1 : -1]
outfile = sys.argv[-1]
# infiles = [
# "c:/users/my_user/slides1.odp",
# "c:/users/my_user/slides2.odp",
# ]
# outfile = "c:/users/my_user/output.odp"
mergeFiles(ctx, desk, infiles, outfile)
if __name__ == '__main__':
asService()
I kilka zdań komentarza:
funkcja asMacro() jest w miarę dokładnym odpowiednikiem Basicowego testMergeFiles(). Ale ... nie działa; konkretnie -- generuje dokument z jednym pustym slajdem. Przypuszczam, że time.sleep pracuje w tym samym wątku co office'owy dispatcher, w przeciwieństwie do Basicowej funkcji wait. Ale to by wymagało dokładniejszego sprawdzenia.
Funkcja asService jest przeznaczona do współpracy z procesem serwerowym Open/LibreOffce'a. Jest kilka metod uruchomienia takiego procesu, ja używałem komunikacji przez port tcp 2002. Office startował z polecenia
Kod: Zaznacz cały
libreoffice.exe -headless "-accept=socket,host=localhost,port=2002;urp;"
(nazwę pliku wykonywalnego należy w razie czego poprzedzić ścieżką, zależnie od szczegółów instalacji pakietu).
Skrypt jest zorganizowany tak, że uruchamia funkcję asService automatycznie. Wymagany do uruchomienia wiersz poleceń ma postać
Kod: Zaznacz cały
"scieżka do libreoffice\python.exe" skrypt.py plik1.odp plik2.odp ... plikN.odp output.odp
co oznacza, że:
- uruchamiany Python powinien należeć do instalacji Open/LibreOffice. Chyba że pracujesz na Linuksie, a Libre jest zainstalowany z paczki przygotowanej przez Twoje distro -- wtedy Python systemowy jest OK;
- skrypt.py to nazwa pliku ze skryptem. W zasadzie dowolna;
- dalej następują argumenty wywołania. Wszystkie, z wyjątkiem ostatniego, są ścieżkami do łączonych dokumentów. Ostatni jest ścieżką pliku wynikowego. Skrypt zamieni je na ścieżki bezwzględne, a następnie na URL-e;
- w związku z tym argument wywołania musi być co najmniej jeden -- co jest sprawdzane.
Skrypt uruchomiony tym sposobem jest skuteczny. W tym przypadku sleep pracuje w wątku klienta, więc nie zamraża działań dispatchera.
Testowałem pod Windowsem; otwieranie plików daje efekty wizualne. Jeżeli serwer pracuje na Linuksie bez X-ów, zdarzyć się to nie może siłą rzeczy.
Skrypt kopiuje zawartość tekstową i graficzną poszczególnych stron, ale ignoruje tzw. Master Pages (czyli wzorce stron) i być może style (nie sprawdzałem dokładnie). W każdym razie robi tyle samo, co ręczne Copy&Paste.
Przy rozbudowie warto pamiętać, że LO ma wbudowanego Pythona 3.x, a Apache OO pozostaje wciąż z wersją 2.x.
// edit
W trybie headless na Linuksie skrypt pracuje, ale jakby nie wszystko kopiował. Czyżby .uno:Copy/Paste bazowały na schowku GUI? nie mogę wykluczyć...
Jakimś rozwiązaniem zawsze pozostaje lekki serwer X w stylu xvfb albo vncserver. Chociaż chciałoby się zrobić to całkiem po cichu.