work in progress: link processing

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3153 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2009-01-17 16:51:09 +00:00
parent 5c5260c3e3
commit 28c88e7176
8 changed files with 188 additions and 50 deletions

View file

@ -4,6 +4,9 @@ Yet Another WikiWiki Framework
($Id$) ($Id$)
>>> from zope import component
>>> from zope.publisher.browser import TestRequest
An Example for an Elementary Wiki Structure An Example for an Elementary Wiki Structure
=========================================== ===========================================
@ -26,9 +29,12 @@ We format the content of the start page using the restructured text format.
The parser for restructured text and a corresponding HTML writer are the The parser for restructured text and a corresponding HTML writer are the
default plugins used, so we can already render the page as HTML. default plugins used, so we can already render the page as HTML.
>>> print startPage.render() >>> print startPage.render(TestRequest())
<p><strong>Welcome to the Demo Wiki</strong></p> <p><strong>Welcome to the Demo Wiki</strong></p>
Links to existing pages
-----------------------
We now create another page that contains a link to the start page. We now create another page that contains a link to the start page.
>>> aboutPage = wiki.createPage('about') >>> aboutPage = wiki.createPage('about')
@ -40,11 +46,36 @@ We now create another page that contains a link to the start page.
... `Back to the Start Page <start_page>`_ ... `Back to the Start Page <start_page>`_
... ''' ... '''
>>> print aboutPage.render() >>> print aboutPage.render(TestRequest())
processing reference:
<reference name="Back to the Start Page"
refuri="start_page">Back to the Start Page</reference>
<p><strong>Information about the Demo Wiki</strong></p> <p><strong>Information about the Demo Wiki</strong></p>
<p>This is the cybertools demo wiki.</p> <p>This is the cybertools demo wiki.</p>
<p><a class="reference" href="start_page">Back to the Start Page</a></p> <p><a class="reference"
href="http://127.0.0.1/demo_wiki/start_page">Back to the Start Page</a></p>
Let's now have a look at the link manager - it should have recorded the link
from the page content.
>>> from cybertools.wiki.interfaces import ILinkManager
>>> linkManager = manager.getPlugin(ILinkManager, 'basic')
>>> links = linkManager.links
>>> len(links)
1
>>> link = links.values()[0]
>>> link.source, link.target, link.name, link.refuri
(0, 1, u'start_page', 'http://127.0.0.1/demo_wiki/start_page')
Links to not yet existing pages
-------------------------------
>>> aboutPage.text += '''
... `More... <more>`_
... '''
>>> print aboutPage.render(TestRequest())
<p><strong>Information about the Demo Wiki</strong></p>
<p>This is the cybertools demo wiki.</p>
<p><a class="reference"
href="http://127.0.0.1/demo_wiki/start_page">Back to the Start Page</a></p>
<p><a class="reference"
href="http://127.0.0.1/demo_wiki/create.html?linkid=0000002">?More...</a></p>

View file

@ -40,7 +40,7 @@ class BaseConfiguration(object):
def getConfig(self, functionality): def getConfig(self, functionality):
c = self.get(functionality) c = self.get(functionality)
if c is None: if c is None:
parent = self.getParent() parent = self.getConfigParent()
if parent is not None: if parent is not None:
return parent.getConfig(functionality) return parent.getConfig(functionality)
return c return c
@ -48,7 +48,7 @@ class BaseConfiguration(object):
def get(self, key, default=None): def get(self, key, default=None):
return getattr(self, key, None) return getattr(self, key, None)
def getParent(self): def getConfigParent(self):
return self.parent return self.parent

View file

@ -22,9 +22,11 @@ Basic (sample) implementations for links and link management
$Id$ $Id$
""" """
from docutils.nodes import Text
from zope.interface import implements from zope.interface import implements
from zope.traversing.browser import absoluteURL
from cybertools.wiki.interfaces import ILink, ILinkManager from cybertools.wiki.interfaces import ILink, ILinkManager, ILinkProcessor
class LinkManager(object): class LinkManager(object):
@ -41,15 +43,31 @@ class LinkManager(object):
link = Link(name, source, target, **kw) link = Link(name, source, target, **kw)
link.manager = self link.manager = self
id = self.generateLinkIdentifier(link) id = self.generateLinkIdentifier(link)
self.linksBySource[source] = self.links[id] = link self.links[id] = link
self.linksBySource.setdefault(source, []).append(link)
return link
def removeLink(self, link): def removeLink(self, link):
if link.identifier in self.links: if link.identifier in self.links:
link.manager = None link.manager = None
del self.links[link.identifier] del self.links[link.identifier]
def query(self, source=None, target=None, name=None, **kw):
if source is None:
result = self.links.values()
else:
result = self.linksBySource.get(source, [])
kw.update(dict(target=target, name=name))
for k, v in kw.items():
if v is None:
continue
if not isinstance(v, (list, tuple)):
v = [v]
result = [r for r in result if getattr(r, k) in v]
return result
def generateLinkIdentifier(self, link): def generateLinkIdentifier(self, link):
identifier = 'l%07i' % (max(self.links.keys() or [0]) + 1) identifier = '%07i' % (max([int(k) for k in self.links.keys()] or [0]) + 1)
link.identifier = identifier link.identifier = identifier
return identifier return identifier
@ -76,5 +94,51 @@ class Link(object):
def __getattr__(self, attr): def __getattr__(self, attr):
if attr not in ILink: if attr not in ILink:
raise AttributeError(attr) raise AttributeError(attr)
return getattr(self, attr, None) return self.__dict__.get(attr)
class LinkProcessor(object):
implements(ILinkProcessor)
parent = None # parent (tree) processor
def __init__(self, context):
self.node = self.context = context
def getProperties(self):
raise ValueError("Method 'getProperties()' must be implemented by subclass.")
def process(self):
#print 'processing reference:', self.node
props = self.getProperties()
source = self.parent.context
wiki = source.getWiki()
manager = wiki.getManager()
sourceUid = manager.getUid(source)
name = props['targetName']
lmName = source.getConfig('linkManager')
lm = wiki.getManager().getPlugin(ILinkManager, lmName)
existing = lm.query(source=sourceUid, name=name)
if existing:
link = existing[0]
target = manager.getObject(link.target)
else:
target = wiki.getPage(name)
targetUid = manager.getUid(target)
link = lm.createLink(name, sourceUid, targetUid)
if link.refuri is None:
request = self.parent.request
if request is not None:
if target is None:
link.refuri = '%s/create.html?linkid=%s' % (
absoluteURL(wiki, request), link.identifier)
else:
link.refuri = absoluteURL(target, request)
self.setProperty('refuri', link.refuri)
if target is None:
# change CSS class, link text
# needs overriding of HTMLTranslator.visit_reference()
self.setProperty('class', 'create') # no direct effect
self.node.insert(0, Text('?'))

View file

@ -38,7 +38,7 @@ class TreeProcessor(object):
implements(ITreeProcessor) implements(ITreeProcessor)
adapts(IWikiPage) adapts(IWikiPage)
tree = None tree = request = None
visitor = None visitor = None
def __init__(self, context): def __init__(self, context):
@ -54,20 +54,17 @@ class Visitor(object):
def __init__(self, context): def __init__(self, context):
self.context = context # the tree processor self.context = context # the tree processor
self.document = context.tree # needed internally self.document = context.tree # needed internally
self.processors = {} # cache
self.processorNames = self.context.context.getConfig('nodeProcessors') self.processorNames = self.context.context.getConfig('nodeProcessors')
def dispatch_visit(self, node): def dispatch_visit(self, node):
#print 'visiting', node.tagname #print 'visiting', node.tagname
tag = node.tagname tag = node.tagname
procs = self.processors.get(tag) procs = []
if procs is None: procNames = self.processorNames.get(tag, [])
procs = self.processors[tag] = [] for n in procNames:
procNames = self.processorNames.get(tag, []) proc = component.queryAdapter(node, INodeProcessor, name=n)
for n in procNames: if proc is not None:
proc = component.queryAdapter(node, INodeProcessor, name=n) proc.parent = self.context
if proc is not None: procs.append(proc)
proc.parent = self.context
procs.append(proc)
for p in procs: for p in procs:
p.process() p.process()

View file

@ -24,6 +24,7 @@ $Id$
from zope import component from zope import component
from zope.interface import implements from zope.interface import implements
from zope.app.intid.interfaces import IIntIds
from cybertools.wiki.interfaces import IWikiConfiguration from cybertools.wiki.interfaces import IWikiConfiguration
from cybertools.wiki.interfaces import IWikiManager, IWiki, IWikiPage from cybertools.wiki.interfaces import IWikiManager, IWiki, IWikiPage
@ -57,9 +58,15 @@ class WikiManager(BaseConfiguration):
def getPlugin(self, type, name): def getPlugin(self, type, name):
return component.getUtility(type, name=name) return component.getUtility(type, name=name)
def getUid(self, obj):
return component.getUtility(IIntIds).getId(obj)
def getObject(self, uid):
return component.getUtility(IIntIds).getObject(uid)
# configuration # configuration
def getParent(self): def getConfigParent(self):
return component.getUtility(IWikiConfiguration) return component.getUtility(IWikiConfiguration)
@ -96,7 +103,7 @@ class Wiki(BaseConfiguration):
# configuration # configuration
def getParent(self): def getConfigParent(self):
return self.getManager() return self.getManager()
@ -114,10 +121,10 @@ class WikiPage(BaseConfiguration):
def getWiki(self): def getWiki(self):
return self.wiki return self.wiki
def render(self): def render(self, request=None):
source = self.preprocess(self.text) source = self.preprocess(self.text)
tree = self.parse(source) tree = self.parse(source)
self.process(tree) self.process(tree, request)
result = self.write(tree) result = self.write(tree)
return self.postprocess(result) return self.postprocess(result)
@ -134,10 +141,11 @@ class WikiPage(BaseConfiguration):
def preprocess(self, source): def preprocess(self, source):
return source return source
def process(self, tree): def process(self, tree, request=None):
processor = component.getAdapter(self, ITreeProcessor, processor = component.getAdapter(self, ITreeProcessor,
name=self.getConfig('processor')) name=self.getConfig('processor'))
processor.tree = tree processor.tree = tree
processor.request = request
processor.process() processor.process()
def postprocess(self, result): def postprocess(self, result):
@ -145,6 +153,6 @@ class WikiPage(BaseConfiguration):
# configuration # configuration
def getParent(self): def getConfigParent(self):
return self.getWiki() return self.getWiki()

View file

@ -26,26 +26,15 @@ from docutils.nodes import reference
from zope.interface import implements from zope.interface import implements
from zope.component import adapts from zope.component import adapts
from cybertools.wiki.interfaces import INodeProcessor, ILinkManager from cybertools.wiki.base.link import LinkProcessor
class Reference(object): class Reference(LinkProcessor):
implements(INodeProcessor)
adapts(reference) adapts(reference)
parent = None # parent (tree) processor def getProperties(self):
return dict(targetName=self.node['refuri'])
def __init__(self, context):
self.node = self.context = context
def process(self):
print 'processing reference:', self.node
source = self.parent.context
wiki = source.getWiki()
sourceName = ':'.join((wiki.name, source.name))
targetName = self.node['refuri']
lmName = source.getConfig('linkManager')
lm = wiki.getManager().getPlugin(ILinkManager, lmName)
target = wiki.getPage(targetName)
def setProperty(self, name, value):
self.node[name] = value

View file

@ -35,7 +35,7 @@ class IWikiConfiguration(Interface):
parser = Attribute('Plug-in component converting from text input ' parser = Attribute('Plug-in component converting from text input '
'format to internal tree format.') 'format to internal tree format.')
def getParent(): def getConfigParent():
""" Return the parent object in case this configuration does not """ Return the parent object in case this configuration does not
provide configuration information for a certain functionality. provide configuration information for a certain functionality.
""" """
@ -66,6 +66,13 @@ class IWikiManager(Interface):
""" Return the plugin of the type given with the name given. """ Return the plugin of the type given with the name given.
""" """
def getUid(obj):
""" Return the unique id of the object given.
"""
def getObject(uid):
""" Return the object referenced by the unique id given.
"""
class IWiki(Interface): class IWiki(Interface):
""" A collection of wiki pages, or - more generally - wiki components. """ A collection of wiki pages, or - more generally - wiki components.
@ -111,7 +118,7 @@ class IWikiPage(Interface):
""" The wiki this page belongs to.' """ The wiki this page belongs to.'
""" """
def render(): def render(request=None):
""" Convert the source text of the page to presentation format. """ Convert the source text of the page to presentation format.
""" """
@ -127,7 +134,7 @@ class IWikiPage(Interface):
""" Modify the source text of the page before parsing it and return it. """ Modify the source text of the page before parsing it and return it.
""" """
def process(tree): def process(tree, request=None):
""" Scan the tree, changing it if necessary and collecting """ Scan the tree, changing it if necessary and collecting
interesting information about the nodes, e.g. about links. interesting information about the nodes, e.g. about links.
""" """
@ -162,6 +169,8 @@ class ITreeProcessor(Interface):
context = Attribute('The wiki page from which the tree was generated.') context = Attribute('The wiki page from which the tree was generated.')
tree = Attribute('The tree to be processed.') tree = Attribute('The tree to be processed.')
request = Attribute('An optional request object, needed e.g. for '
'rendering absolute URLs.')
def process(): def process():
""" Do what is necessary. """ Do what is necessary.
@ -179,6 +188,11 @@ class INodeProcessor(Interface):
""" Do what is necessary. """ Do what is necessary.
""" """
def getProperties():
""" Return a dictionary of properties provided by the context
node that may be needed for processing.
"""
# wiki elements # wiki elements
@ -187,7 +201,7 @@ class ILinkManager(Interface):
""" """
def createLink(name, source, target, **kw): def createLink(name, source, target, **kw):
""" Create and register a link record. """ Create, register, and return a link record.
Optional attributes are given as keyword arguments. Optional attributes are given as keyword arguments.
""" """
@ -231,6 +245,7 @@ class ILink(Interface):
'for external links - the target URI.') 'for external links - the target URI.')
targetFragment = Attribute('Optional: an address part leading to a ' targetFragment = Attribute('Optional: an address part leading to a '
'text anchor or the part of an image.') 'text anchor or the part of an image.')
refuri = Attribute('The URI linking to the target object.')
user = Attribute('Optional: a string denoting the creator of the record.') user = Attribute('Optional: a string denoting the creator of the record.')
run = Attribute('Optional: May be used to group the links from a certain ' run = Attribute('Optional: May be used to group the links from a certain '
'source at different times.') 'source at different times.')
@ -238,3 +253,9 @@ class ILink(Interface):
def getManager(): def getManager():
""" Return the link manager this link is managed by. """ Return the link manager this link is managed by.
""" """
class ILinkProcessor(INodeProcessor):
""" A node processor specialized on links (references).
"""

View file

@ -9,13 +9,38 @@ $Id$
import unittest, doctest import unittest, doctest
from zope.testing.doctestunit import DocFileSuite from zope.testing.doctestunit import DocFileSuite
from zope import component from zope import component
from zope.interface import implements
from zope.app.intid.interfaces import IIntIds
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.traversing.browser.interfaces import IAbsoluteURL
from cybertools.relation.tests import IntIdsStub
from cybertools.wiki.base.config import WikiConfiguration from cybertools.wiki.base.config import WikiConfiguration
from cybertools.wiki.base.process import TreeProcessor from cybertools.wiki.base.process import TreeProcessor
from cybertools.wiki.base.link import LinkManager from cybertools.wiki.base.link import LinkManager
from cybertools.wiki.dcu.html import Writer as DocutilsHTMLWriter from cybertools.wiki.dcu.html import Writer as DocutilsHTMLWriter
from cybertools.wiki.dcu.rstx import Parser as DocutilsRstxParser from cybertools.wiki.dcu.rstx import Parser as DocutilsRstxParser
from cybertools.wiki.dcu import process from cybertools.wiki.dcu import process
from cybertools.wiki.interfaces import IWiki, IWikiPage
class WikiURL(object):
implements(IAbsoluteURL)
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self):
return '%s/%s' % (self.request.URL, self.context.name)
class PageURL(WikiURL):
def __call__(self):
return '%s/%s' % (WikiURL(self.context.getWiki(), self.request)(),
self.context.name)
class Test(unittest.TestCase): class Test(unittest.TestCase):
@ -26,6 +51,9 @@ class Test(unittest.TestCase):
def setUp(testCase): def setUp(testCase):
component.provideAdapter(WikiURL, (IWiki, IBrowserRequest), IAbsoluteURL)
component.provideAdapter(PageURL, (IWikiPage, IBrowserRequest), IAbsoluteURL)
component.provideUtility(IntIdsStub())
component.provideUtility(WikiConfiguration()) component.provideUtility(WikiConfiguration())
component.provideUtility(DocutilsHTMLWriter(), name='docutils.html') component.provideUtility(DocutilsHTMLWriter(), name='docutils.html')
component.provideUtility(DocutilsRstxParser(), name='docutils.rstx') component.provideUtility(DocutilsRstxParser(), name='docutils.rstx')