General purpose OOo writer/impress (Word/PPT) import

plus fix for systems without OOo

bzr-revno: 793
This commit is contained in:
Jonathan Corwin 2010-04-13 21:42:22 +01:00
commit 7f8077ec2c
5 changed files with 496 additions and 273 deletions

View File

@ -27,4 +27,5 @@ from manager import SongManager
from songstab import SongsTab from songstab import SongsTab
from mediaitem import SongMediaItem from mediaitem import SongMediaItem
from sofimport import SofImport from sofimport import SofImport
from oooimport import OooImport
from songimport import SongImport from songimport import SongImport

View File

@ -0,0 +1,197 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2010 Raoul Snyman #
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
# Gorven, Scott Guerrieri, Christian Richter, Maikel Stuivenberg, Martin #
# Thompson, Jon Tibble, Carsten Tinggaard #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import os
from PyQt4 import QtCore
from songimport import SongImport
if os.name == u'nt':
from win32com.client import Dispatch
PAGE_BEFORE = 4
PAGE_AFTER = 5
PAGE_BOTH = 6
else:
try:
import uno
from com.sun.star.style.BreakType import PAGE_BEFORE, PAGE_AFTER, PAGE_BOTH
except:
pass
class OooImport(object):
"""
Import songs from Impress/Powerpoint docs using Impress
"""
def __init__(self, songmanager):
"""
Initialise the class. Requires a songmanager class which is passed
to SongImport for writing song to disk
"""
self.song = None
self.manager = songmanager
self.document = None
self.process_started = False
def import_docs(self, filenames):
self.start_ooo()
for filename in filenames:
filename = unicode(filename)
if os.path.isfile(filename):
self.open_ooo_file(filename)
if self.document:
if self.document.supportsService(
"com.sun.star.presentation.PresentationDocument"):
self.process_pres()
if self.document.supportsService(
"com.sun.star.text.TextDocument"):
self.process_doc()
self.close_ooo_file()
self.close_ooo()
def start_ooo(self):
"""
Start OpenOffice.org process
TODO: The presentation/Impress plugin may already have it running
"""
if os.name == u'nt':
self.start_ooo_process()
self.desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop')
else:
context = uno.getComponentContext()
resolver = context.ServiceManager.createInstanceWithContext(
u'com.sun.star.bridge.UnoUrlResolver', context)
ctx = None
loop = 0
while ctx is None and loop < 5:
try:
ctx = resolver.resolve(u'uno:socket,host=localhost,' \
+ 'port=2002;urp;StarOffice.ComponentContext')
except:
pass
self.start_ooo_process()
loop += 1
manager = ctx.ServiceManager
self.desktop = manager.createInstanceWithContext(
"com.sun.star.frame.Desktop", ctx )
def start_ooo_process(self):
try:
if os.name == u'nt':
self.manager = Dispatch(u'com.sun.star.ServiceManager')
self.manager._FlagAsMethod(u'Bridge_GetStruct')
self.manager._FlagAsMethod(u'Bridge_GetValueObject')
else:
cmd = u'openoffice.org -nologo -norestore -minimized -invisible ' \
+ u'-nofirststartwizard ' \
+ '-accept="socket,host=localhost,port=2002;urp;"'
process = QtCore.QProcess()
process.startDetached(cmd)
process.waitForStarted()
self.process_started = True
except:
pass
def open_ooo_file(self, filepath):
"""
Open the passed file in OpenOffice.org Impress
"""
if os.name == u'nt':
url = u'file:///' + filepath.replace(u'\\', u'/')
url = url.replace(u':', u'|').replace(u' ', u'%20')
else:
url = uno.systemPathToFileUrl(filepath)
properties = []
properties = tuple(properties)
try:
self.document = self.desktop.loadComponentFromURL(url, u'_blank',
0, properties)
if not self.document.supportsService(
"com.sun.star.presentation.PresentationDocument") \
and not self.document.supportsService(
"com.sun.star.text.TextDocument"):
self.close_ooo_file()
except:
pass
return
def close_ooo_file(self):
"""
Close file.
"""
self.document.close(True)
self.document = None
def close_ooo(self):
"""
Close OOo. But only if we started it and not on windows
"""
if self.process_started:
self.desktop.terminate()
def process_pres(self):
"""
Process the file
"""
doc = self.document
slides = doc.getDrawPages()
text = u''
for slide_no in range(slides.getCount()):
slide = slides.getByIndex(slide_no)
slidetext = u''
for idx in range(slide.getCount()):
shape = slide.getByIndex(idx)
if shape.supportsService("com.sun.star.drawing.Text"):
if shape.getString().strip() != u'':
slidetext += shape.getString().strip() + u'\n\n'
if slidetext.strip() == u'':
slidetext = u'\f'
text += slidetext
song = SongImport(self.manager)
songs = SongImport.process_songs_text(self.manager, text)
for song in songs:
song.finish()
return
def process_doc(self):
"""
Process the doc file, a paragraph at a time
"""
text = u''
paragraphs = self.document.getText().createEnumeration()
while paragraphs.hasMoreElements():
paratext = u''
paragraph = paragraphs.nextElement()
if paragraph.supportsService("com.sun.star.text.Paragraph"):
textportions = paragraph.createEnumeration()
while textportions.hasMoreElements():
textportion = textportions.nextElement()
if textportion.BreakType in (PAGE_BEFORE, PAGE_BOTH):
paratext += u'\f'
paratext += textportion.getString()
if textportion.BreakType in (PAGE_AFTER, PAGE_BOTH):
paratext += u'\f'
text += paratext + u'\n'
songs = SongImport.process_songs_text(self.manager, text)
for song in songs:
song.finish()

View File

@ -29,12 +29,12 @@
# http://www.oooforum.org/forum/viewtopic.phtml?t=14409 # http://www.oooforum.org/forum/viewtopic.phtml?t=14409
# http://wiki.services.openoffice.org/wiki/Python # http://wiki.services.openoffice.org/wiki/Python
import os
import re import re
import os
import time
from PyQt4 import QtCore from PyQt4 import QtCore
from songimport import SongImport from songimport import SongImport
from oooimport import OooImport
if os.name == u'nt': if os.name == u'nt':
from win32com.client import Dispatch from win32com.client import Dispatch
@ -49,7 +49,7 @@ else:
from com.sun.star.awt.FontSlant import ITALIC from com.sun.star.awt.FontSlant import ITALIC
from com.sun.star.style.BreakType import PAGE_BEFORE, PAGE_AFTER, PAGE_BOTH from com.sun.star.style.BreakType import PAGE_BEFORE, PAGE_AFTER, PAGE_BOTH
class SofImport(object): class SofImport(OooImport):
""" """
Import songs provided on disks with the Songs of Fellowship music books Import songs provided on disks with the Songs of Fellowship music books
VOLS1_2.RTF, sof3words.rtf and sof4words.rtf VOLS1_2.RTF, sof3words.rtf and sof4words.rtf
@ -70,83 +70,16 @@ class SofImport(object):
Initialise the class. Requires a songmanager class which is passed Initialise the class. Requires a songmanager class which is passed
to SongImport for writing song to disk to SongImport for writing song to disk
""" """
self.song = None OooImport.__init__(self,songmanager)
self.manager = songmanager
self.process_started = False
def import_sof(self, filename): def import_sof(self, filename):
self.start_ooo() self.start_ooo()
self.open_ooo_file(filename) self.open_ooo_file(filename)
self.process_doc() self.process_sof_file()
self.close_ooo_file()
self.close_ooo() self.close_ooo()
def start_ooo(self): def process_sof_file(self):
"""
Start OpenOffice.org process
TODO: The presentation/Impress plugin may already have it running
"""
if os.name == u'nt':
self.start_ooo_process()
self.desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop')
else:
context = uno.getComponentContext()
resolver = context.ServiceManager.createInstanceWithContext(
u'com.sun.star.bridge.UnoUrlResolver', context)
ctx = None
loop = 0
while ctx is None and loop < 5:
try:
ctx = resolver.resolve(u'uno:socket,host=localhost,' \
+ 'port=2002;urp;StarOffice.ComponentContext')
except:
pass
self.start_ooo_process()
loop += 1
manager = ctx.ServiceManager
self.desktop = manager.createInstanceWithContext(
"com.sun.star.frame.Desktop", ctx )
def start_ooo_process(self):
try:
if os.name == u'nt':
self.manager = Dispatch(u'com.sun.star.ServiceManager')
self.manager._FlagAsMethod(u'Bridge_GetStruct')
self.manager._FlagAsMethod(u'Bridge_GetValueObject')
else:
cmd = u'openoffice.org -nologo -norestore -minimized -invisible ' \
+ u'-nofirststartwizard ' \
+ '-accept="socket,host=localhost,port=2002;urp;"'
process = QtCore.QProcess()
process.startDetached(cmd)
process.waitForStarted()
self.process_started = True
except:
pass
def open_ooo_file(self, filepath):
"""
Open the passed file in OpenOffice.org Writer
"""
if os.name == u'nt':
url = u'file:///' + filepath.replace(u'\\', u'/')
url = url.replace(u':', u'|').replace(u' ', u'%20')
else:
url = uno.systemPathToFileUrl(filepath)
properties = []
properties = tuple(properties)
self.document = self.desktop.loadComponentFromURL(url, u'_blank',
0, properties)
def close_ooo(self):
"""
Close RTF file. Note, on Windows we'll leave OOo running
Leave running on Windows
"""
self.document.close(True)
if self.process_started:
self.desktop.terminate()
def process_doc(self):
""" """
Process the RTF file, a paragraph at a time Process the RTF file, a paragraph at a time
""" """
@ -247,7 +180,7 @@ class SofImport(object):
into line into line
""" """
text = textportion.getString() text = textportion.getString()
text = self.tidy_text(text) text = SongImport.tidy_text(text)
if text.strip() == u'': if text.strip() == u'':
return text return text
if textportion.CharWeight == BOLD: if textportion.CharWeight == BOLD:
@ -320,17 +253,7 @@ class SofImport(object):
"Mr Smith" and "Mrs Smith". "Mr Smith" and "Mrs Smith".
""" """
text = text.replace(u' and ', u' & ') text = text.replace(u' and ', u' & ')
for author in text.split(u','): self.song.parse_author(text)
authors = author.split(u'&')
for i in range(len(authors)):
author2 = authors[i].strip()
if author2.find(u' ') == -1 and i < len(authors) - 1:
author2 = author2 + u' ' \
+ authors[i + 1].strip().split(u' ')[-1]
if author2.endswith(u'.'):
author2 = author2[:-1]
if author2:
self.song.add_author(author2)
def add_verse_line(self, text): def add_verse_line(self, text):
""" """
@ -380,21 +303,6 @@ class SofImport(object):
self.currentverse = u'' self.currentverse = u''
self.is_chorus = False self.is_chorus = False
def tidy_text(self, text):
"""
Get rid of some dodgy unicode and formatting characters we're not
interested in. Some can be converted to ascii.
"""
text = text.replace(u'\t', u' ')
text = text.replace(u'\r', u'\n')
text = text.replace(u'\u2018', u'\'')
text = text.replace(u'\u2019', u'\'')
text = text.replace(u'\u201c', u'"')
text = text.replace(u'\u201d', u'"')
text = text.replace(u'\u2026', u'...')
text = text.replace(u'\u2013', u'-')
text = text.replace(u'\u2014', u'-')
return text
def uncap_text(self, text): def uncap_text(self, text):
""" """

View File

@ -24,6 +24,7 @@
############################################################################### ###############################################################################
import string import string
from PyQt4 import QtGui, QtCore
from openlp.core.lib import SongXMLBuilder from openlp.core.lib import SongXMLBuilder
from openlp.plugins.songs.lib.models import Song, Author, Topic, Book from openlp.plugins.songs.lib.models import Song, Author, Topic, Book
@ -59,6 +60,79 @@ class SongImport(object):
self.verses = [] self.verses = []
self.versecount = 0 self.versecount = 0
self.choruscount = 0 self.choruscount = 0
self.copyright_string = unicode(QtGui.QApplication.translate( \
u'SongImport', u'copyright'))
self.copyright_symbol = unicode(QtGui.QApplication.translate( \
u'SongImport', u'©'))
@staticmethod
def process_songs_text(manager, text):
songs = []
songtexts = SongImport.tidy_text(text).split(u'\f')
song = SongImport(manager)
for songtext in songtexts:
if songtext.strip():
song.process_song_text(songtext.strip())
if song.check_complete():
songs.append(song)
song = SongImport(manager)
if song.check_complete():
songs.append(song)
return songs
@staticmethod
def tidy_text(text):
"""
Get rid of some dodgy unicode and formatting characters we're not
interested in. Some can be converted to ascii.
"""
text = text.replace(u'\t', u' ')
text = text.replace(u'\r\n', u'\n')
text = text.replace(u'\r', u'\n')
text = text.replace(u'\u2018', u'\'')
text = text.replace(u'\u2019', u'\'')
text = text.replace(u'\u201c', u'"')
text = text.replace(u'\u201d', u'"')
text = text.replace(u'\u2026', u'...')
text = text.replace(u'\u2013', u'-')
text = text.replace(u'\u2014', u'-')
# Remove surplus blank lines, spaces, trailing/leading spaces
while text.find(u' ') >= 0:
text = text.replace(u' ', u' ')
text = text.replace(u'\n ', u'\n')
text = text.replace(u' \n', u'\n')
text = text.replace(u'\n\n\n\n\n', u'\f')
text = text.replace(u'\f ', u'\f')
text = text.replace(u' \f', u'\f')
while text.find(u'\f\f') >= 0:
text = text.replace(u'\f\f', u'\f')
return text
def process_song_text(self, text):
versetexts = text.split(u'\n\n')
for versetext in versetexts:
if versetext.strip() != u'':
self.process_verse_text(versetext.strip())
def process_verse_text(self, text):
lines = text.split(u'\n')
if text.lower().find(self.copyright_string) >= 0 \
or text.lower().find(self.copyright_symbol) >= 0:
copyright_found = False
for line in lines:
if copyright_found or line.lower().find(self.copyright_string) >= 0\
or line.lower().find(self.copyright_symbol) >= 0:
copyright_found = True
self.add_copyright(line)
else:
self.parse_author(line)
return
if len(lines) == 1:
self.parse_author(lines[0])
return
if not self.get_title():
self.set_title(lines[0])
self.add_verse(text)
def get_title(self): def get_title(self):
""" """
@ -107,17 +181,40 @@ class SongImport(object):
""" """
Build the copyright field Build the copyright field
""" """
if self.copyright.find(copyright) >= 0:
return
if self.copyright != u'': if self.copyright != u'':
self.copyright += ' ' self.copyright += ' '
self.copyright += copyright self.copyright += copyright
def add_author(self, text): def parse_author(self, text):
"""
Add the author. OpenLP stores them individually so split by 'and', '&'
and comma.
However need to check for "Mr and Mrs Smith" and turn it to
"Mr Smith" and "Mrs Smith".
"""
for author in text.split(u','):
authors = author.split(u'&')
for i in range(len(authors)):
author2 = authors[i].strip()
if author2.find(u' ') == -1 and i < len(authors) - 1:
author2 = author2 + u' ' \
+ authors[i + 1].strip().split(u' ')[-1]
if author2.endswith(u'.'):
author2 = author2[:-1]
if author2:
self.add_author(author2)
def add_author(self, author):
""" """
Add an author to the list Add an author to the list
""" """
self.authors.append(text) if author in self.authors:
return
self.authors.append(author)
def add_verse(self, verse, versetag): def add_verse(self, verse, versetag=None):
""" """
Add a verse. This is the whole verse, lines split by \n Add a verse. This is the whole verse, lines split by \n
Verse tag can be V1/C1/B etc, or 'V' and 'C' (will count the verses/ Verse tag can be V1/C1/B etc, or 'V' and 'C' (will count the verses/
@ -129,13 +226,13 @@ class SongImport(object):
if oldverse.strip() == verse.strip(): if oldverse.strip() == verse.strip():
self.verse_order_list.append(oldversetag) self.verse_order_list.append(oldversetag)
return return
if versetag == u'V' or not versetag:
self.versecount += 1
versetag = u'V' + unicode(self.versecount)
if versetag.startswith(u'C'): if versetag.startswith(u'C'):
self.choruscount += 1 self.choruscount += 1
if versetag == u'C': if versetag == u'C':
versetag += unicode(self.choruscount) versetag += unicode(self.choruscount)
if versetag == u'V' or not versetag:
self.versecount += 1
versetag = u'V' + unicode(self.versecount)
self.verses.append([versetag, verse.rstrip()]) self.verses.append([versetag, verse.rstrip()])
self.verse_order_list.append(versetag) self.verse_order_list.append(versetag)
if versetag.startswith(u'V') and self.contains_verse(u'C1'): if versetag.startswith(u'V') and self.contains_verse(u'C1'):

View File

@ -29,7 +29,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import Plugin, build_icon, PluginStatus, Receiver from openlp.core.lib import Plugin, build_icon, PluginStatus, Receiver
from openlp.plugins.songs.lib import SongManager, SongMediaItem, SongsTab, \ from openlp.plugins.songs.lib import SongManager, SongMediaItem, SongsTab, \
SofImport SofImport, OooImport
from openlp.plugins.songs.forms import OpenLPImportForm, OpenSongExportForm, \ from openlp.plugins.songs.forms import OpenLPImportForm, OpenSongExportForm, \
OpenSongImportForm, OpenLPExportForm OpenSongImportForm, OpenLPExportForm
@ -105,11 +105,14 @@ class SongsPlugin(Plugin):
self.ImportOpenlp2Item.setObjectName(u'ImportOpenlp2Item') self.ImportOpenlp2Item.setObjectName(u'ImportOpenlp2Item')
self.ImportSofItem = QtGui.QAction(import_menu) self.ImportSofItem = QtGui.QAction(import_menu)
self.ImportSofItem.setObjectName(u'ImportSofItem') self.ImportSofItem.setObjectName(u'ImportSofItem')
self.ImportOooItem = QtGui.QAction(import_menu)
self.ImportOooItem.setObjectName(u'ImportOooItem')
# Add to menus # Add to menus
self.ImportSongMenu.addAction(self.ImportOpenlp1Item) self.ImportSongMenu.addAction(self.ImportOpenlp1Item)
self.ImportSongMenu.addAction(self.ImportOpenlp2Item) self.ImportSongMenu.addAction(self.ImportOpenlp2Item)
self.ImportSongMenu.addAction(self.ImportOpenSongItem) self.ImportSongMenu.addAction(self.ImportOpenSongItem)
self.ImportSongMenu.addAction(self.ImportSofItem) self.ImportSongMenu.addAction(self.ImportSofItem)
self.ImportSongMenu.addAction(self.ImportOooItem)
import_menu.addAction(self.ImportSongMenu.menuAction()) import_menu.addAction(self.ImportSongMenu.menuAction())
# Translations... # Translations...
self.ImportSongMenu.setTitle(import_menu.trUtf8('&Song')) self.ImportSongMenu.setTitle(import_menu.trUtf8('&Song'))
@ -132,6 +135,12 @@ class SongsPlugin(Plugin):
self.ImportSofItem.setStatusTip( self.ImportSofItem.setStatusTip(
import_menu.trUtf8('Import songs from the VOLS1_2.RTF, sof3words' \ import_menu.trUtf8('Import songs from the VOLS1_2.RTF, sof3words' \
+ '.rtf and sof4words.rtf supplied with the music books')) + '.rtf and sof4words.rtf supplied with the music books'))
self.ImportOooItem.setText(
import_menu.trUtf8('Generic Document/Presentation Import'))
self.ImportOooItem.setToolTip(
import_menu.trUtf8('Import songs from Word/Writer/Powerpoint/Impress'))
self.ImportOooItem.setStatusTip(
import_menu.trUtf8('Import songs from Word/Writer/Powerpoint/Impress'))
# Signals and slots # Signals and slots
QtCore.QObject.connect(self.ImportOpenlp1Item, QtCore.QObject.connect(self.ImportOpenlp1Item,
QtCore.SIGNAL(u'triggered()'), self.onImportOpenlp1ItemClick) QtCore.SIGNAL(u'triggered()'), self.onImportOpenlp1ItemClick)
@ -141,6 +150,8 @@ class SongsPlugin(Plugin):
QtCore.SIGNAL(u'triggered()'), self.onImportOpenSongItemClick) QtCore.SIGNAL(u'triggered()'), self.onImportOpenSongItemClick)
QtCore.QObject.connect(self.ImportSofItem, QtCore.QObject.connect(self.ImportSofItem,
QtCore.SIGNAL(u'triggered()'), self.onImportSofItemClick) QtCore.SIGNAL(u'triggered()'), self.onImportSofItemClick)
QtCore.QObject.connect(self.ImportOooItem,
QtCore.SIGNAL(u'triggered()'), self.onImportOooItemClick)
self.ImportSongMenu.menuAction().setVisible(False) self.ImportSongMenu.menuAction().setVisible(False)
def add_export_menu_item(self, export_menu): def add_export_menu_item(self, export_menu):
@ -184,10 +195,11 @@ class SongsPlugin(Plugin):
self.opensong_import_form.show() self.opensong_import_form.show()
def onImportSofItemClick(self): def onImportSofItemClick(self):
filename = QtGui.QFileDialog.getOpenFileName( filenames = QtGui.QFileDialog.getOpenFileNames(
None, self.trUtf8('Open Songs of Fellowship file'), None, self.trUtf8('Open Songs of Fellowship file'),
u'', u'Songs of Fellowship file (*.rtf *.RTF)') u'', u'Songs of Fellowship file (*.rtf *.RTF)')
try: try:
for filename in filenames:
sofimport = SofImport(self.songmanager) sofimport = SofImport(self.songmanager)
sofimport.import_sof(unicode(filename)) sofimport.import_sof(unicode(filename))
except: except:
@ -202,6 +214,14 @@ class SongsPlugin(Plugin):
QtGui.QMessageBox.Ok) QtGui.QMessageBox.Ok)
Receiver.send_message(u'load_song_list') Receiver.send_message(u'load_song_list')
def onImportOooItemClick(self):
filenames = QtGui.QFileDialog.getOpenFileNames(
None, self.trUtf8('Open documents or presentations'),
u'', u'All Files(*.*)')
oooimport = OooImport(self.songmanager)
oooimport.import_docs(filenames)
Receiver.send_message(u'load_song_list')
def onExportOpenlp1ItemClicked(self): def onExportOpenlp1ItemClicked(self):
self.openlp_export_form.show() self.openlp_export_form.show()