######################################################################
# zwiki page parenting functionality
from string import join
from types import *
from urllib import quote
import Acquisition
import Permissions
###########################################################################
# CLASS ZwikiParentsMixin
# RESPONSIBILITIES help generate table of contents
# COLLABORATORS ZWikiPage
###########################################################################
class ZWikiParentsMixin:
"""
This mix-in class encapsulates ZWikiPage's page parenting
functionality. I had to leave several related code fragments over
in ZWikiPage.py but I think it's worth doing.
"""
######################################################################
# CLASS VARIABLES
######################################################################
parents = []
_properties=(
{'id':'parents', 'type': 'lines', 'mode': 'w'},
)
__ac_permissions__=(
(Permissions.Reparent, ('reparent',)),
)
######################################################################
# METHOD CATEGORY: parenting
######################################################################
def reparent(self, parents=None, REQUEST=None):
"""Reset parents property according to request."""
if parents is None:
parents = REQUEST.get('parents', None)
if parents is None:
self.parents = []
else:
if type(parents) != ListType:
parents = [parents]
self.parents = parents
self._set_last_editor(REQUEST)
if REQUEST is not None:
REQUEST.RESPONSE.redirect(REQUEST['URL1'])
def get_ancestors(self):
"""Return a trimmed nesting structure indicating this page's ancestors.
See the WikiNesting docstring for nestings structure description."""
container = self.aq_parent
ancestors = {}
offspring = {}
tops = {} # Ancestors that have no parents.
todo = {self.id(): None}
while todo:
doing = todo
todo = {}
for i in doing.keys():
if ancestors.has_key(i):
continue # We've already collected this one.
else:
if not hasattr(container, i):
continue # Absent - ignore it.
ancestors[i] = None
obj = container[i]
if not hasattr(obj, 'parents'):
continue
parents = obj.parents
if type(parents) != ListType: # SKWM
parents = []
if parents:
for p in parents:
if offspring.has_key(p):
offspring[p].append(i)
else:
offspring[p] = [i]
todo[p] = None
else: tops[i] = None
# Ok, now go back down, unravelling each forebear only once:
tops = tops.keys()
tops.sort
did = {}; got = []
for t in tops:
got.append(descend_ancestors(t, ancestors, did, offspring))
return got
def context(self, REQUEST=None, with_siblings=0, enlarge_current=0):
"""Return HTML showing this page's parents and siblings."""
myid = self.id()
if with_siblings:
try:
nesting = WikiNesting(self.aq_parent).get_up_and_back(myid)
except:
nesting=()
else:
nesting = self.get_ancestors()
if (len(nesting) == 0 or
(len(nesting) == 1 and len(nesting[0]) == 1)) and not enlarge_current:
return " "
#SKWM XXX
if self.aq_parent.title:
#titletxt = ' for %s' % (self.aq_parent.title)
titletxt = '%s ' % (self.aq_parent.title)
else:
titletxt = ''
#SKWM XXX
#special case: if we are creating a new page, display it's title
if REQUEST.has_key('page') and REQUEST['page'] is not myid:
myid = REQUEST['page']
nesting = self.deepappend(nesting, myid)
suppress_hyperlink=1
else:
suppress_hyperlink=0
#SKWM XXX
#detect when parents have gotten confused & reset if necessary
hierarchy = present_nesting(myid, nesting, self.wiki_url(),
enlarge_current=enlarge_current,
suppress_hyperlink=suppress_hyperlink)
if hierarchy == '
':
self.parents = []
hierarchy = present_nesting(myid, nesting, self.wiki_url(),
enlarge_current=enlarge_current,
suppress_hyperlink=suppress_hyperlink)
# return '%s sisukord\n' % \
return ''
# return ''
def deepappend(self, nesting, pageid):
"""append a page to the very bottom of a nesting."""
if type(nesting[-1]) is type([]):
nesting[-1] = self.deepappend(nesting[-1], pageid)
return nesting
else:
if len(nesting) is 1:
nesting.append(pageid)
else:
nesting[-1] = [nesting[-1],pageid]
return nesting
def offspring(self, REQUEST=None):
"""Return a presentation of all my offspring."""
myid = self.id()
nesting = WikiNesting(self.aq_parent)
return present_nesting(myid, nesting.get_offspring([myid]),
self.wiki_url()) # SKWM
def map(self, REQUEST=None):
"""Present the nesting layout of the entire wiki, showing:
- All the independent nodes, ie those without parents or children, and
- All the branches in the wiki - from the possibly multiple roots."""
import string
map = WikiNesting(self.aq_parent).get_map()
singletons = []
combos = []
rel = self.wiki_url() # SKWM
for i in map:
if type(i) == StringType:
singletons.append('%s' # SKWM add named targets
% (rel, quote(i), quote(i), i))
else:
combos.append(i)
return ("Singletons
The rest
%s "
% (join(singletons," "),
present_nesting(self.id(), combos, rel)))
###########################################################################
# CLASS WikiNesting
# RESPONSIBILITIES
# COLLABORATORS
###########################################################################
class WikiNesting(Acquisition.Implicit):
"""Given a wiki directory, generate nesting relationship from parents info.
In a nesting, nodes are represented as:
- Leaves: the string name of the page
- Nodes with children: a list beginning with the parent node's name
- Nodes with omitted children (for brevity): list with one string.
"""
# XXX We could make this a persistent object and minimize recomputes.
# Put it in a standard place in the wiki's folder, or have the
# wikis in a folder share an instance, but use a single
# persistent one which need not recompute all the relationship
# maps every time - just needs to compare all pages parents
# settings with the last noticed parents settings, and adjust
# the children, roots, and parents maps just for those that
# changed. On this first cut we just recompute it all...
######################################################################
# METHOD CATEGORY: misc
######################################################################
def __init__(self, container):
self.container = container
self.set_nesting()
def set_nesting(self):
"""Preprocess for easy derivation of nesting structure.
We set:
- .parentmap: {'node1': ['parent1', ...], ...}
- .childmap: {'node1': ['child1', ...], ...}
- .roots: {'root1': None, ...}"""
pages = self.container.objectValues(spec='ZWiki Page')
pagenames = []
parentmap = {} # 'node': ['parent1', ...]
childmap = {} # 'node': ['child1', ...]
roots = {}
for pg in pages:
parents = pg.parents
if type(parents) != ListType: # SKWM
parents = []
pgnm = pg.id()
pagenames.append(pgnm)
# Parents is direct:
parentmap[pgnm] = parents[:]
# We have a root if node acknowledges no parents:
if not parents:
roots[pgnm] = None
if not childmap.has_key(pgnm):
childmap[pgnm] = []
# Register page as child for all its parents:
for p in parents:
# We can't just append, in case we're a persistent object
# (which recognizes the need to update by an attr assignment).
if childmap.has_key(p): pchildren = childmap[p]
else: pchildren = []
if pgnm not in pchildren: pchildren.append(pgnm)
childmap[p] = pchildren
for k in parentmap.keys():
parentmap[k].sort()
for k in childmap.keys():
childmap[k].sort()
self.parentmap = parentmap
self.childmap = childmap
self.roots = roots
def get_map(self):
"""Return nesting of entire wiki."""
did = {}
got = []
roots = self.roots.keys()
roots.sort()
return self.get_offspring(roots)
def get_offspring(self, pages, did=None):
"""Return nesting showing all offspring of a list of wiki pagenames.
did is used for recursion, to prune already elaborated pages."""
if did is None: did = {}
got = []
for p in pages:
been_there = did.has_key(p)
did[p] = None
if self.childmap.has_key(p):
children = self.childmap[p]
if children:
subgot = [p]
if not been_there:
subgot.extend(self.get_offspring(children, did=did))
got.append(subgot)
else:
got.append(p)
else:
got.append(p)
return got
def get_up_and_back(self, pagename):
"""Return nesting showing page containment and immediate offspring."""
# Go up, identifying all and topmost forbears:
ancestors = {} # Ancestors of pagename
tops = {} # Ancestors that have no parents
todo = {pagename: None}
parentmap = self.parentmap
while todo:
doing = todo
todo = {}
for i in doing.keys():
if ancestors.has_key(i):
continue # We already took care of this one.
else:
ancestors[i] = None
if parentmap.has_key(i):
parents = parentmap[i]
if parents:
for p in parents:
todo[p] = None
else: tops[i] = None
ancestors[pagename] = None # Finesse inclusion of page's offspring
# Ok, now go back down, showing offspring of all intervening ancestors:
tops = tops.keys()
tops.sort
did = {}; got = []
childmap = self.childmap
for t in tops:
got.append(descend_ancestors(t, ancestors, did, childmap))
return got
######################################################################
# FUNCTION CATEGORY: parenting helper functions
######################################################################
def present_nesting(myid, nesting, rel, did=None, _got=None, indent="", \
enlarge_current=0, suppress_hyperlink=0):
# aaaah! dooo something...
"""
Present an HTML outline of a nesting structure.
_got is used internally and should not be passed in except when recursing.
See WikiNesting docstring for nesting structure description.
"""
if did is None: did = []
if _got is None:
_got = [""]
recursing = 0
else:
recursing = 1
for n in nesting:
if type(n) == ListType:
if n[0] == myid:
# The entry is the current node - distinguish it.
if enlarge_current:
nodenm = '%s' % n[0]
if suppress_hyperlink:
_got.append('%s - %s' % (indent, nodenm))
else:
_got.append('%s
- %s'
% (indent, rel, quote(n[0]), nodenm))
else:
nodenm = "%s" % n[0]
_got.append('%s
- %s'
% (indent, rel, quote(n[0]), quote(n[0]), nodenm))
else:
nodenm = n[0]
_got.append('%s
- %s'
% (indent, rel, quote(n[0]), quote(n[0]), nodenm))
if len(n) > 1:
_got.append("
")
for i in n[1:]:
if type(i) == ListType:
_got = present_nesting(myid, [i], rel, did=did,
_got=_got, indent=indent+' ',
enlarge_current=enlarge_current,
suppress_hyperlink=suppress_hyperlink)
else:
if i == myid:
# Distinguish that entry is for current node.
if enlarge_current:
inm = '' + i + ''
if suppress_hyperlink:
_got.append('%s - %s' % (indent, inm))
else:
_got.append('%s
- %s' % (indent, rel, quote(i), inm))
else:
inm = "" + i + ""
_got.append('%s
- %s' % (indent, rel, quote(i), quote(i), inm))
else:
inm = i
_got.append('%s
- %s' % (indent, rel, quote(i), quote(i), inm))
_got.append("
")
else:
# Parents whose children were omitted - indicate:
_got[-1] = _got[-1] + " ..."
else:
if n == myid:
# The entry is for the current node - distinguish.
if enlarge_current:
inm = '' + n + ''
if suppress_hyperlink:
_got.append('%s - %s' % (indent, inm))
else:
_got.append('%s
- %s' % (indent, rel, quote(n), inm))
else:
inm = "" + n + ""
_got.append('%s
- %s' % (indent, rel, quote(n), quote(n), inm))
else:
inm = n
_got.append('%s
- %s' % (indent, rel, quote(n), quote(n), inm))
if recursing:
return _got
else:
_got.append("
")
return join(_got, "\n")
def descend_ancestors(page, ancestors, did, children):
"""Create nesting of ancestors leading to page.
page is the name of the subject page.
ancestors is a mapping whose keys are pages that are ancestors of page
children is a mapping whose keys are pages children, and the values
are the children's parents.
Do not repeat ones we already did."""
got = []
for c in ((children.has_key(page) and children[page]) or []):
if not ancestors.has_key(c):
# We don't descend offspring that are not ancestors.
got.append(c)
elif ((children.has_key(c) and children[c]) or []):
if did.has_key(c):
# We only show offspring of ancestors once...
got.append([c])
else:
# ... and this is the first time.
did[c] = None
got.append(descend_ancestors(c, ancestors, did, children))
else:
got.append(c)
got.sort() # Terminals will come before composites.
got.insert(0, page)
return got