#@+leo-ver=4-thin
#@+node:ekr.20060904103412:@thin leoOPML.py
#@<< docstring >>
#@+node:ekr.20060904103412.1:<< docstring >>
r'''A plugin to read and write Leo outlines in .opml (http://en.wikipedia.org/wiki/OPML) format.
Warning: the OPML plugin is not fully functional at present. Use with caution.
The OPML plugin creates two new commands that read and write Leo outlines in OPML format.
The read-opml-file command creates a Leo outline from an.opmlfile. import
The write-opml-file command writes the present Leo outline to an .opml file.
Various settings control what gets written to .opml files, and in what format.
As usual, you specify settings for the OPML plugin using leoSettings.leo.
The settigns for the OPML are found in the node: @settings-->Plugins-->opml plugin
Here are the settings that control the format of .opml files. The default values are shown.
- @string opml_namespace = leo:com:leo-opml-version-1
The namespace urn for the xmlns attribute of <opml> elements.
This value typically is not used, but it should refer to Leo in some way.
- @bool opml_use_outline_elements = True
- If True, Leo writes body text to <leo:body> elements nested in <outline> elements.
Otherwise, Leo writes body text to leo:body attributes of <outline> elements.
- @string opml_version = 2.0
The opml version string written to the <OPML> element.
Use 2.0 unless there is a specific reason to use 1.0.
- @bool opml_write_body_text = True
Leo writes body text to the OPML file only if this is True.
- @bool opml_write_leo_details = True
If True, Leo writes the native attributes of Leo's <v> elements as attributes of
the opml <outline> elements. The native attributes of <v> elements are a,
descendentTnodeUnknownAttributes, expanded, marks, t, and tnodeList.
- @bool opml_write_leo_globals_attributes = True
If True, Leo writes body_outline_ratio` and global_window_position attributes to
the <head> element of the .opml file.
- @bool opml_write_ua_attributes
If True, write unknownAttributes **NOTE** ua_attributes are not currently read from opml import
- @bool opml_expand_ua_dictionary
If True, expand an unknownAttriubte 'x' of type dict to 'ua_x_key0', 'ua_x_key1' etc.
**WARNING** using this feature may prevent reading these ua_attributes from opml import
if that feature is implemented in the future.
- @bool opml_skip_ua_dictionary_blanks
If True, when expanding as above, skip blank dict entries.
'''
#@nonl
#@-node:ekr.20060904103412.1:<< docstring >>
#@nl
#@@language python
#@@tabwidth -4
#@@pagewidth 80
#@<< to do >>
#@+node:ekr.20060920112018:<< to do >>
#@@nocolor
#@+at
#
# - Enhance open/save commands when this plugin is active.
#
# - read/write uA's.
#@-at
#@-node:ekr.20060920112018:<< to do >>
#@nl
__version__ = '0.93'
# For traces.
printElements = [] # ['all','outline','head','body',]
#@<< version history >>
#@+node:ekr.20060904103412.2:<< version history >>
#@@killcolor
#@+at
#
# 0.01 EKR: Initial version.
# 0.02 EKR: This plugin overrides leoFileCommands.fileCommands.putToOPML.
# - Changes to Leo's core:
# - Added toOPML=False keyword argument to write_Leo_file.
# - Added dummy putToOPML method.
# 0.03 EKR: Use SAX to read .opml files. Parsing works. More semantics are
# needed.
# 0.04 EKR: Simplified the code.
# 0.05 EKR: Outline created with proper headlines: (wrote & debugged
# createVnodes & helpers)
# 0.06 EKR: Rewrote createChildren. It's simpler and appears to handle clones
# properly.
# 0.07 EKR: Revised code using saxRead plugin as a model.
# All strings are now assumed to be unicode.
# 0.08 EKR: Leo can now read and write body and headline text properly.
# - Use xml.sax.saxutils to quote attributes properly.
# - Improved error handling in parse_opml_file.
# 0.1 EKR: Write and read a attributes (marks, expanded, etc.)
# 0.1.1 EKR: Added resolveTnodeLists and related logic.
# 0.1.2 EKR: Moved parse_opml_file into opmlController class, as is done in
# the saxRead plugin.
# 0.2 EKR: Moved putToOPML and its helpers to Leo's core, so there is no need
# to subclass any fileCommands class.
# Future plugins could customize these methods, but configuration settings
# would be better.
# 0.3 EKR: Do not call toUnicode: sax should already have done that.
# 0.4 EKR: No change to opml plugin, but defined fc.attributeEscape to convert
# newlines to ' \n'
# This overcomes the sax parser's tendency to strip newlines from attributes.
# Sheesh.
# 0.5 EKR: Added support for namespaces. The beginnings of opml settings.
# 0.6 EKR: Moved write code back into plugin. It is more convenient and
# flexible.
# - Added support for opml_use_outline_elements, opml_write_leo_details and
# opml_write_body_text
# settings in leoSettings.leo.
# - The read code no longer requires tnx fields.
# 0.7 EKR: Support for opml_version. Reading and writing works (except for
# tnodeLists) regardless of settings.
# 0.8 EKR: This plugin now creates the read/write-opml-file commands.
# 0.9 EKR: Improved docstring.
# 0.91 EKR: reading derived files appears to work:
# - create c.fileCommands.tnodeDict before calling c.atFileCommands.readAll.
# - Properly init t.fileIndex to an actual gnx using scanGnx.
# 0.92 EKR: Support for opml_write_leo_globals_attributes setting:
# - Read and write leo:body_outline_ratio attribute and
# <leo:global_window_position> element.
# 0.93 EKR: Support opml_read_derived_files & opml_write_derived_files
# 0.94 TNB: write unknownAttributes, use leo: rather than just : for
# namespace, tweaked SAX content reading
#@-at
#@-node:ekr.20060904103412.2:<< version history >>
#@nl
#@<< imports >>
#@+node:ekr.20060904103412.3:<< imports >>
import leo.core.leoGlobals as g
import leo.core.leoPlugins as leoPlugins
import leo.core.leoNodes as leoNodes
import leo.core.leoFileCommands as leoFileCommands
import xml.sax
import xml.sax.saxutils
#@nonl
#@-node:ekr.20060904103412.3:<< imports >>
#@nl
#@+others
#@+node:ekr.20060904132527.9:Module level
#@+node:ekr.20060904103412.4:init
def init ():
# Override the base class
leoPlugins.registerHandler('start1',onStart2)
# Register the commands.
leoPlugins.registerHandler(('open2','new'),onCreate)
g.plugin_signon(__name__)
return True
#@nonl
#@-node:ekr.20060904103412.4:init
#@+node:ekr.20060904103412.5:onCreate
def onCreate (tag, keys):
c = keys.get('c')
if c:
opmlController(c)
#@nonl
#@-node:ekr.20060904103412.5:onCreate
#@+node:ekr.20060919172012:onStart2
def onStart2 (tag,keys):
# Override the fileCommands class by opmlFileCommandsClass.
leoFileCommands.fileCommands = opmlFileCommandsClass
#@nonl
#@-node:ekr.20060919172012:onStart2
#@-node:ekr.20060904132527.9:Module level
#@+node:ekr.20060919172012.1:class opmlFileCommandsClass (fileCommands)
class opmlFileCommandsClass (leoFileCommands.fileCommands):
'''An subclass of Leo's core fileCommands class that
should do *nothing* other than override putToOPML.'''
#@ @+others
#@+node:ekr.20060919172012.2:putToOPML & helpers
# All elements and attributes prefixed by 'leo:' are leo-specific.
# All other elements and attributes are specified by the OPML 1 spec.
def putToOPML (self):
c = self.c
for ivar in (
'opml_use_outline_elements',
'opml_write_derived_files',
'opml_write_leo_details',
'opml_write_leo_globals_attributes',
'opml_write_body_text',
'opml_write_ua_attributes',
'opml_expand_ua_dictionary',
'opml_skip_ua_dictionary_blanks',
):
setattr(self,ivar,c.config.getBool(ivar))
self.putXMLLine()
self.putOPMLProlog()
self.putOPMLHeader()
self.putOPMLNodes()
self.putOPMLPostlog()
#@nonl
#@+node:ekr.20060919172012.3:putOPMLProlog
def putOPMLProlog (self):
s = self.c.config.getString('opml_namespace') or 'leo:com:leo-opml'
ver = self.c.config.getString('opml_version') or '2.0'
self.put('<opml version="%s" xmlns:leo="%s">' % (ver,s))
#@-node:ekr.20060919172012.3:putOPMLProlog
#@+node:ekr.20060919172012.4:putOPMLHeader
def putOPMLHeader (self):
'''Put the OPML header, including attributes for globals, prefs and find settings.'''
c = self.c ; indent = ' ' * 4
if self.opml_write_leo_globals_attributes:
self.put('\n<head leo:body_outline_ratio="%s">' % str(c.frame.ratio))
width,height,left,top = c.frame.get_window_info()
self.put('\n%s<leo:global_window_position' % indent)
self.put(' top="%s" left="%s" height="%s" width="%s"/>' % (
str(top),str(left),str(height),str(width)))
self.put('\n</head>')
else:
self.put('\n<head/>')
#@-node:ekr.20060919172012.4:putOPMLHeader
#@+node:ekr.20060919172012.5:putOPMLNodes
def putOPMLNodes (self):
c = self.c ; root = c.rootPosition()
self.put('\n<body>')
for p in root.self_and_siblings_iter():
self.putOPMLNode(p)
self.put('\n</body>')
#@nonl
#@-node:ekr.20060919172012.5:putOPMLNodes
#@+node:ekr.20060919172012.6:putOPMLNode
def putOPMLNode (self,p):
c = self.c
indent = ' ' * (4 * p.level()) # Always use 4-space indents.
body = p.bodyString() or '' ; head = p.headString() or ''
attrFormat = ' %s="%s"'
self.put('\n%s<outline' % indent)
if self.opml_write_leo_details: # Put leo-specific attributes.
for name,val in (
('leo:t', g.app.nodeIndices.toString(p.v.t.fileIndex)),
('leo:a', self.aAttributes(p)),
('leo:tnodeList',self.tnodeListAttributes(p)),
):
if val: self.put(attrFormat % (name,val))
data = self.uAAttributes(p)
if data:
for name,val in data.iteritems():
self.put(attrFormat % (name,val))
self.put(attrFormat % ('text',self.attributeEscape(head)))
closed = False
if body and self.opml_write_body_text:
if self.opml_use_outline_elements:
self.put('>') ; closed = True
self.put('<leo:body>%s</leo:body>' % xml.sax.saxutils.escape(body))
else:
self.put(attrFormat % ('leo:body',self.attributeEscape(body)))
if p.hasChildren():
if not closed:
self.put('>') ; closed = True
for p2 in p.children_iter():
self.putOPMLNode(p2)
if closed:
self.put('\n%s</outline>' % indent)
# self.put('</outline>\n')
else:
self.put('/>')
#@nonl
#@+node:ekr.20060919172012.7:attributeEscape
def attributeEscape(self,s):
# Unlike xml.sax.saxutils.escape, replace " by " and replace newlines by character reference.
s = s or ''
return (
s.replace('&','&')
.replace('<','<')
.replace('>','>')
.replace('"','"')
.replace('\n',' \n')
)
#@-node:ekr.20060919172012.7:attributeEscape
#@+node:ekr.20060919172012.8:aAttributes
def aAttributes (self,p):
c = self.c
attr = []
if p.isExpanded(): attr.append('E')
if p.isMarked(): attr.append('M')
if c.isCurrentPosition(p): attr.append('V')
#if p.v.isOrphan(): attr.append('O')
#if p.equal(self.topPosition): attr.append('T')
return ''.join(attr)
#@nonl
#@-node:ekr.20060919172012.8:aAttributes
#@+node:ekr.20060919172012.9:tnodeListAttributes
# Based on fileCommands.putTnodeList.
def tnodeListAttributes (self,p):
'''Put the tnodeList attribute of p.v.t'''
# Remember: entries in the tnodeList correspond to @+node sentinels, _not_ to tnodes!
if not hasattr(p.v.t,'tnodeList') or not p.v.t.tnodeList:
return None
# g.trace('tnodeList',p.v.t.tnodeList)
# Assign fileIndices.
for t in p.v.t.tnodeList:
try: # Will fail for None or any pre 4.1 file index.
theId,time,n = p.v.t.fileIndex
except:
g.trace("assigning gnx for ",p.v.t)
gnx = g.app.nodeIndices.getNewIndex()
p.v.t.setFileIndex(gnx) # Don't convert to string until the actual write.
s = ','.join([g.app.nodeIndices.toString(t.fileIndex) for t in p.v.t.tnodeList])
return s
#@nonl
#@-node:ekr.20060919172012.9:tnodeListAttributes
#@+node:tbrown.20061004094757:uAAttributes
def uAAttributes(self, p):
"""write unknownAttributes with various levels of expansion"""
data = {}
if self.opml_write_ua_attributes and hasattr(p.v, 'unknownAttributes'):
for uak, uav in p.v.unknownAttributes.iteritems():
if self.opml_expand_ua_dictionary and type(uav) == type({}):
for uakc, uavc in uav.iteritems():
if str(uavc)!='' or not self.opml_skip_ua_dictionary_blanks:
data['leo:ua_'+uak+'_'+uakc] = self.attributeEscape(str(uavc))
else:
data['leo:ua_'+uak] = self.attributeEscape(str(uav))
return data
#@nonl
#@-node:tbrown.20061004094757:uAAttributes
#@-node:ekr.20060919172012.6:putOPMLNode
#@+node:ekr.20060919172012.11:putOPMLPostlog
def putOPMLPostlog (self):
self.put('\n</opml>\n')
#@nonl
#@-node:ekr.20060919172012.11:putOPMLPostlog
#@-node:ekr.20060919172012.2:putToOPML & helpers
#@-others
#@nonl
#@-node:ekr.20060919172012.1:class opmlFileCommandsClass (fileCommands)
#@+node:ekr.20060904103412.6:class opmlController
class opmlController:
#@ @+others
#@+node:ekr.20060904103412.7:__init__
def __init__ (self,c):
self.c = c
c.opmlCommands = self
c.k.registerCommand('read-opml-file',None,self.readOpmlCommand,pane='all',verbose=False)
c.k.registerCommand('write-opml-file',None,self.writeOpmlCommand,pane='all',verbose=False)
self.opml_read_derived_files = c.config.getBool('opml_read_derived_files')
self.opml_write_derived_files = c.config.getBool('opml_write_derived_files')
self.currentVnode = None
self.topVnode = None
self.generatedTnxs = {} # Keys are tnx's (strings). Values are vnodes.
#@nonl
#@-node:ekr.20060904103412.7:__init__
#@+node:ekr.20060904103412.8:createCommands (not used)
def createCommands (self):
c = self.c
c.opmlCommands = self
if 0:
for name,func in (
('read-opml-file', self.readFile),
('write-opml-file', self.writeFile),
):
c.k.registerCommand (name,shortcut=None,func=func,pane='all',verbose=False)
#@nonl
#@-node:ekr.20060904103412.8:createCommands (not used)
#@+node:ekr.20060914163456:createVnodes & helpers
def createVnodes (self, dummyRoot):
'''**Important**: this method and its helpers are low-level code
corresponding to link/unlink methods in leoNodes.py.
Modify this with extreme care.'''
self.generatedTnxs = {}
children = self.createChildren(dummyRoot,parent_v = None)
firstChild = children and children[0]
return firstChild
#@+node:ekr.20060914171659.2:createChildren
# node is a nodeClass object, parent_v is a vnode.
def createChildren (self, node, parent_v):
result = []
for child in node.children:
tnx = child.tnx
v = tnx and self.generatedTnxs.get(tnx)
if v:
# A clone. Create a new clone node, but share the subtree, i.e., the tnode.
# g.trace('clone',child.headString)
v = self.createVnode(child,parent_v,t=v.t)
else:
v = self.createVnodeTree(child,parent_v)
if tnx: self.generatedTnxs [tnx] = v
result.append(v)
self.linkSiblings(result)
if parent_v: self.linkParentAndChildren(parent_v,result)
return result
#@nonl
#@-node:ekr.20060914171659.2:createChildren
#@+node:ekr.20060914171659:createVnodeTree
def createVnodeTree (self,node,parent_v):
v = self.createVnode(node,parent_v)
# To do: create the children only if v is not a clone.
self.createChildren(node,v)
return v
#@nonl
#@-node:ekr.20060914171659:createVnodeTree
#@+node:ekr.20060914171659.1:createVnode & helpers
def createVnode (self,node,parent_v,t=None):
h = node.headString
b = node.bodyString
if not node.tnx or not t:
t = leoNodes.tnode(bodyString=b,headString=h)
if node.tnx:
t.fileIndex = g.app.nodeIndices.scanGnx(node.tnx,0)
v = leoNodes.vnode(t)
v.t.vnodeList.append(v)
v._parent = parent_v
self.handleVnodeAttributes(node,v)
return v
#@nonl
#@+node:ekr.20060917213611:handleVnodeAttributes
def handleVnodeAttributes (self,node,v):
a = node.attributes.get('leo:a')
if a:
# 'C' (clone) and 'D' bits are not used.
if 'M' in a: v.setMarked()
if 'E' in a: v.expand()
if 'O' in a: v.setOrphan()
if 'T' in a: self.topVnode = v
if 'V' in a: self.currentVnode = v
s = node.attributes.get('leo:tnodeList')
tnodeList = s and s.split(',')
if tnodeList:
# This tnode list will be resolved later.
# g.trace('found tnodeList',v.headString(),len(tnodeList))
v.tempTnodeList = tnodeList
#@nonl
#@-node:ekr.20060917213611:handleVnodeAttributes
#@-node:ekr.20060914171659.1:createVnode & helpers
#@+node:ekr.20060914174806:linkParentAndChildren
def linkParentAndChildren (self, parent_v, children):
# if children: g.trace(parent_v,len(children))
firstChild_v = children and children[0] or None
parent_v.t._firstChild = firstChild_v
for child in children:
child._parent = parent_v
v = parent_v
if v not in v.t.vnodeList:
v.t.vnodeList.append(v)
#@nonl
#@-node:ekr.20060914174806:linkParentAndChildren
#@+node:ekr.20060914165257:linkSiblings
def linkSiblings (self, sibs):
'''Set the v._back and v._next links for all vnodes v in sibs.'''
n = len(sibs)
for i in xrange(n):
v = sibs[i]
v._back = (i-1 >= 0 and sibs[i-1]) or None
v._next = (i+1 < n and sibs[i+1]) or None
#@nonl
#@-node:ekr.20060914165257:linkSiblings
#@-node:ekr.20060914163456:createVnodes & helpers
#@+node:ekr.20060913220707:dumpTree
def dumpTree (self,root,dummy):
if not dummy:
root.dump()
for child in root.children:
self.dumpTree(child,dummy=False)
#@nonl
#@-node:ekr.20060913220707:dumpTree
#@+node:ekr.20060904134958.116:parse_opml_file
def parse_opml_file (self,inputFileName):
if not inputFileName or not inputFileName.endswith('.opml'):
return None
c = self.c
path = g.os_path_normpath(g.os_path_join(g.app.loadDir,inputFileName))
try: f = open(path)
except IOError:
g.trace('can not open %s'%path)
return None
try:
try:
node = None
parser = xml.sax.make_parser()
# Do not include external general entities.
# The actual feature name is "http://xml.org/sax/features/external-general-entities"
parser.setFeature(xml.sax.handler.feature_external_ges,0)
handler = contentHandler(c,inputFileName)
parser.setContentHandler(handler)
parser.parse(f)
node = handler.getNode()
except xml.sax.SAXParseException:
g.es_print('Error parsing %s' % (inputFileName),color='red')
g.es_exception()
return None
except Exception:
g.es_print('Unexpected exception parsing %s' % (inputFileName),color='red')
g.es_exception()
return None
finally:
f.close()
return node
#@nonl
#@-node:ekr.20060904134958.116:parse_opml_file
#@+node:ekr.20060904103721:readFile
def readFile (self,fileName):
if not fileName: return
c = self.c
# Pass one: create the intermediate nodes.
self.dummyRoot = dummyRoot = self.parse_opml_file(fileName)
# self.dumpTree(dummyRoot,dummy=True)
# Pass two: create the tree of vnodes and tnodes from the intermediate nodes.
v = self.createVnodes(dummyRoot)
if v:
c2 = c.new()
c2.setRootVnode(v)
if self.opml_read_derived_files:
at = c2.atFileCommands
c2.fileCommands.tnodesDict = self.createTnodesDict()
# g.trace('c2',id(c2),'tnodesDict',id(c2.fileCommands.tnodesDict))
self.resolveTnodeLists(c2)
if self.opml_read_derived_files:
c2.atFileCommands.readAll(c2.rootPosition())
c2.checkOutline()
self.setCurrentPosition(c2)
c2.redraw()
return c2 # for testing.
#@nonl
#@+node:ekr.20060921153603:createTnodesDict
def createTnodesDict (self):
''' c.tnodesDict by from self.generatedTnxs import
by onverting vnode entries to tnodes.'''
d = {}
for key in self.generatedTnxs.keys():
v = self.generatedTnxs.get(key)
# g.trace('v.t',id(v.t),'fileIndex',v.t.fileIndex,v.headString()[:20])
d[key] = v.t
return d
#@nonl
#@-node:ekr.20060921153603:createTnodesDict
#@+node:ekr.20060917214140:setCurrentPosition
def setCurrentPosition (self,c):
v = self.currentVnode
if not v: return
for p in c.allNodes_iter():
if p.v == v:
c.selectPosition(p)
break
#@nonl
#@-node:ekr.20060917214140:setCurrentPosition
#@+node:ekr.20060918132045:resolveTnodeLists
def resolveTnodeLists (self,c):
for p in c.allNodes_iter():
if hasattr(p.v,'tempTnodeList'):
result = []
for tnx in p.v.tempTnodeList:
v = self.generatedTnxs.get(tnx)
if v:
# g.trace('found',v,tnx,v.t)
result.append(v.t)
else:
g.trace('No tnode for %s' % tnx)
p.v.t.tnodeList = result
delattr(p.v,'tempTnodeList')
#@nonl
#@-node:ekr.20060918132045:resolveTnodeLists
#@-node:ekr.20060904103721:readFile
#@+node:ekr.20060919201810:readOpmlCommand
def readOpmlCommand (self,event=None):
'''Open a Leo window containing the contents of an .opml file.'''
c = self.c
fileName = g.app.gui.runOpenFileDialog(
title = "Read OPML",
filetypes = [("OPML files","*.opml"), ("All files","*")],
defaultextension = ".opml")
c.bringToFront()
if fileName and len(fileName) > 0:
c2 = self.readFile(fileName)
else:
c.bodyWantsFocus()
#@nonl
#@-node:ekr.20060919201810:readOpmlCommand
#@+node:ekr.20060904103721.1:writeFile
def writeFile (self,fileName):
if not fileName: return
self.c.fileCommands.write_Leo_file(
fileName,
outlineOnlyFlag=not self.opml_write_derived_files,
toString=False,toOPML=True)
g.es_print('wrote %s' % fileName)
#@nonl
#@-node:ekr.20060904103721.1:writeFile
#@+node:ekr.20060919201330:writeOpmlCommand
def writeOpmlCommand (self,event=None):
'''Save a Leo outline to an OPMLfile.'''
c = self.c
if g.app.disableSave:
g.es("Save commands disabled",color="purple")
return
# Make sure we never pass None to the ctor.
if not c.mFileName:
c.frame.title = ""
initialfile = g.ensure_extension(c.mFileName, ".opml")
# set local fileName, _not_ c.mFileName
fileName = g.app.gui.runSaveFileDialog(
initialfile = initialfile,
title="Write OPML",
filetypes=[("OPML files", "*.opml")],
defaultextension=".opml")
c.bringToFront()
if fileName:
fileName = g.ensure_extension(fileName, ".opml")
c.opmlCommands.writeFile(fileName)
#@nonl
#@-node:ekr.20060919201330:writeOpmlCommand
#@-others
#@nonl
#@-node:ekr.20060904103412.6:class opmlController
#@+node:ekr.20060904141220:class nodeClass
class nodeClass:
'''A class representing one outline element.
Use getters to access the attributes, properties and rules of this mode.'''
#@ @+others
#@+node:ekr.20060904141220.1: node.__init__
def __init__ (self):
self.attributes = {}
self.bodyString = ''
self.headString = ''
self.children = []
self.tnx = None
#@nonl
#@-node:ekr.20060904141220.1: node.__init__
#@+node:ekr.20060904141220.2: node.__str__ & __repr__
def __str__ (self):
return '<node: %s>' % self.headString
__repr__ = __str__
#@nonl
#@-node:ekr.20060904141220.2: node.__str__ & __repr__
#@+node:ekr.20060913220507:dump
def dump (self):
print()
print('node: tnx: %s %s' % (self.tnx,self.headString))
print('children:',[child for child in self.children])
print('attrs:',self.attributes.values())
#@nonl
#@-node:ekr.20060913220507:dump
#@-others
#@nonl
#@-node:ekr.20060904141220:class nodeClass
#@+node:ekr.20060904134958.164:class contentHandler (XMLGenerator)
class contentHandler (xml.sax.saxutils.XMLGenerator):
'''A sax content handler class that reads OPML files.'''
#@ @+others
#@+node:ekr.20060904134958.165: __init__ & helpers
def __init__ (self,c,inputFileName):
self.c = c
self.inputFileName = inputFileName
# Init the base class.
xml.sax.saxutils.XMLGenerator.__init__(self)
#@ << define dispatch dict >>
#@+node:ekr.20060917185525:<< define dispatch dict >>
# There is no need for an 'end' method if all info is carried in attributes.
self.dispatchDict = {
'body': (None,None),
'head': (self.startHead,None),
'opml': (None,None),
'outline': (self.startOutline,self.endOutline),
'leo:body': (self.startBodyText,self.endBodyText),
'leo:global_window_position': (self.startWinPos,None)
}
#@nonl
#@-node:ekr.20060917185525:<< define dispatch dict >>
#@nl
# Semantics.
self.content = []
self.elementStack = []
self.errors = 0
self.level = 0
self.node = None
self.nodeStack = []
self.ratio = 0.5 # body-outline ratio.
self.rootNode = None
#@nonl
#@-node:ekr.20060904134958.165: __init__ & helpers
#@+node:ekr.20060904134958.166:helpers
#@+node:ekr.20060904134958.167:attrsToList
def attrsToList (self,attrs):
'''Convert the attributes to a list of g.Bunches.
attrs: an Attributes item passed to startElement.'''
return [g.Bunch(name=name,val=attrs.getValue(name)) for name in attrs.getNames()]
#@nonl
#@-node:ekr.20060904134958.167:attrsToList
#@+node:ekr.20060904134958.170:error
def error (self, message):
print()
print()
print('XML error: %s' % (message))
print()
self.errors += 1
#@nonl
#@-node:ekr.20060904134958.170:error
#@+node:ekr.20060917185525.1:inElement
def inElement (self,name):
return self.elementStack and name in self.elementStack
#@nonl
#@-node:ekr.20060917185525.1:inElement
#@+node:ekr.20060904134958.171:printStartElement & helpers
def printStartElement(self,name,attrs):
indent = '\t' * self.level or ''
if attrs.getLength() > 0:
print('%s<%s %s>' % (
indent,
self.clean(name).strip(),
self.attrsToString(attrs,sep=' ')))
else:
print('%s<%s>' % (
indent,
self.clean(name).strip()))
if name.lower() in ['outline','head','body',]:
print
#@nonl
#@+node:ekr.20060904134958.168:attrsToString
def attrsToString (self,attrs,sep='\n'):
'''Convert the attributes to a string.
attrs: an Attributes item passed to startElement.
sep: the separator charater between attributes.'''
result = [
'%s="%s"' % (bunch.name,bunch.val)
for bunch in self.attrsToList(attrs)
]
return sep.join(result)
#@nonl
#@-node:ekr.20060904134958.168:attrsToString
#@+node:ekr.20060904134958.169:clean
def clean(self,s):
return g.toEncodedString(s,"ascii")
#@nonl
#@-node:ekr.20060904134958.169:clean
#@-node:ekr.20060904134958.171:printStartElement & helpers
#@-node:ekr.20060904134958.166:helpers
#@+node:ekr.20060904134958.174: Do nothing...
#@+node:ekr.20060904134958.175:other methods
def ignorableWhitespace(self):
g.trace()
def processingInstruction (self,target,data):
g.trace()
def skippedEntity(self,name):
g.trace(name)
def startElementNS(self,name,qname,attrs):
g.trace(name)
def endElementNS(self,name,qname):
g.trace(name)
#@nonl
#@-node:ekr.20060904134958.175:other methods
#@+node:ekr.20060904134958.176:endDocument
def endDocument(self):
pass
#@-node:ekr.20060904134958.176:endDocument
#@+node:ekr.20060904134958.177:startDocument
def startDocument(self):
pass
#@nonl
#@-node:ekr.20060904134958.177:startDocument
#@-node:ekr.20060904134958.174: Do nothing...
#@+node:ekr.20060904134958.178:characters
def characters(self,content):
name = self.elementStack and self.elementStack[-1].lower() or '<no element name>'
# Opml elements should not have content: everything is carried in attributes.
if name == 'leo:body':
if self.node:
self.content.append(content)
else:
self.error('No node for leo:body content')
else:
if content.strip():
print('content:',name,repr(content))
#@nonl
#@-node:ekr.20060904134958.178:characters
#@+node:ekr.20060904134958.179:endElement & helpers
def endElement(self,name):
name = name.lower()
if name in printElements or 'all' in printElements:
indent = '\t' * (self.level-1) or ''
print('%s</%s>' % (indent,self.clean(name).strip()))
data = self.dispatchDict.get(name)
if data is None:
g.trace('unknown element',name)
else:
junk,func = data
if func:
func()
name2 = self.elementStack.pop()
assert name == name2
#@nonl
#@+node:ekr.20060919193501:endBodyText
def endBodyText (self):
'''End a <leo:body> element.'''
if self.content:
self.node.bodyString = ''.join(self.content)
self.content = []
#@nonl
#@-node:ekr.20060919193501:endBodyText
#@+node:ekr.20060917185948:endOutline
def endOutline (self):
self.level -= 1
self.node = self.nodeStack.pop()
#@nonl
#@-node:ekr.20060917185948:endOutline
#@-node:ekr.20060904134958.179:endElement & helpers
#@+node:ekr.20060904134958.180:startElement & helpers
def startElement(self,name,attrs):
name = name.lower()
if name in printElements or 'all' in printElements:
self.printStartElement(name,attrs)
self.elementStack.append(name)
data = self.dispatchDict.get(name)
if data is None:
g.trace('unknown element',name)
else:
func,junk = data
if func:
func(attrs)
#@nonl
#@+node:ekr.20060919193501.1:startBodyText
def startBodyText (self,attrs):
'''Start a <leo:body> element.'''
self.content = []
#@nonl
#@-node:ekr.20060919193501.1:startBodyText
#@+node:ekr.20060922072852:startHead
def startHead (self,attrs):
if not self.inElement('opml'):
self.error('<head> outside <opml>')
self.doHeadAttributes(attrs)
#@nonl
#@+node:ekr.20060922072852.1:doHeadAttributes
def doHeadAttributes (self,attrs):
ratio = 0.5
for bunch in self.attrsToList(attrs):
name = bunch.name ; val = bunch.val
if name == 'leo:body_outline_ratio':
try:
ratio = float(val)
# g.trace(ratio)
except ValueError:
pass
self.ratio = ratio
#@nonl
#@-node:ekr.20060922072852.1:doHeadAttributes
#@-node:ekr.20060922072852:startHead
#@+node:ekr.20060917190349:startOutline
def startOutline (self,attrs):
if self.inElement('head'): self.error('<outline> inside <head>')
if not self.inElement('body'): self.error('<outline> outside <body>')
self.level += 1
if self.rootNode:
parent = self.node
else:
self.rootNode = parent = nodeClass() # The dummy parent node.
parent.headString = 'dummyNode'
self.node = nodeClass()
parent.children.append(self.node)
self.doOutlineAttributes(attrs)
self.nodeStack.append(parent)
#@nonl
#@+node:ekr.20060904141220.34:doOutlineAttributes
def doOutlineAttributes (self,attrs):
node = self.node
for bunch in self.attrsToList(attrs):
name = bunch.name ; val = bunch.val
if name == 'text': # Text is the 'official' opml attribute for headlines.
node.headString = val
elif name == 'leo:body':
#g.trace(repr(val))
#g.es_dump (val[:30])
node.bodyString = val
elif name == 'leo:t':
node.tnx = val
else:
node.attributes[name] = val
#@nonl
#@-node:ekr.20060904141220.34:doOutlineAttributes
#@-node:ekr.20060917190349:startOutline
#@+node:ekr.20060922071010:startWinPos
def startWinPos (self,attrs):
if not self.inElement('head'):
self.error('<leo:global_window_position> outside <body>')
self.doGlobalWindowAttributes(attrs)
#@+node:ekr.20060922071010.1:doGlobalWindowAttributes
def doGlobalWindowAttributes (self,attrs):
c = self.c
top = 50 ; left = 50 ; height = 500 ; width = 700 # Reasonable defaults.
try:
for bunch in self.attrsToList(attrs):
name = bunch.name ; val = bunch.val
if name == 'top': top = int(val)
elif name == 'left': left = int(val)
elif name == 'height': height = int(val)
elif name == 'width': width = int(val)
except ValueError:
pass
# g.trace(top,left,height,width)
c.frame.setTopGeometry(width,height,left,top)
c.frame.deiconify()
c.frame.lift()
c.frame.update()
#@nonl
#@-node:ekr.20060922071010.1:doGlobalWindowAttributes
#@-node:ekr.20060922071010:startWinPos
#@-node:ekr.20060904134958.180:startElement & helpers
#@+node:ekr.20060904134958.183:getNode
def getNode (self):
return self.rootNode
#@nonl
#@-node:ekr.20060904134958.183:getNode
#@-others
#@nonl
#@-node:ekr.20060904134958.164:class contentHandler (XMLGenerator)
#@-others
#@nonl
#@-node:ekr.20060904103412:@thin leoOPML.py
#@-leo
|