#! /usr/bin/env python
# ______________________________________________________________________
"""Module ANOIki
Jonathan Riehl
$Id: ANOIki.py,v 1.8 2005/01/21 00:28:05 jriehl Exp $
"""
# ______________________________________________________________________
# Module imports
import re
import types
import string
import ANOIBaseSpace
import ANOITrie
import ANOITypes
import ANOITime
import ANOIUtil
# ______________________________________________________________________
# Module data
CHAR_MAX = 256
# FIXME: Parts of this regular expression are hacky; would like to stick as
# close to RFC 2396's absolute URI syntax as possible.
SCHEME_RE = "(([a-zA-Z][^\s:/?#]*):)"
PATH_RE = "(//([^\s/?#]*))([^\s?#\)\]]*)"
QUERY_RE = "(\\?([\s^#\)\]]*))?"
FRAGMENT_RE = "(#([^\s\)\]]*))?"
URL_RE_STR = "(%s%s%s%s)" % (SCHEME_RE, PATH_RE, QUERY_RE, FRAGMENT_RE)
urlReObj = re.compile(URL_RE_STR)
# This is also kinda hacky...
WIKI_RE_STR = "([A-Z][a-z]+([A-Z][a-z]+)+)"
wikiReObj = re.compile(WIKI_RE_STR)
UID_RE_STR = "(Uid[0-9]+)"
uidReObj = re.compile(UID_RE_STR)
RE_STR = "(%s)" % string.join([URL_RE_STR, WIKI_RE_STR, UID_RE_STR], "|")
reObj = re.compile(RE_STR)
# ______________________________________________________________________
# Class definitions
class ANOIkiFrontEnd (object):
"""Class ANOIkiFrontEnd
"""
# ____________________________________________________________
def __init__ (self, space, rootNSUid):
"""ANOITexter.__init__()
Constructor for the ANOIkiFrontEnd class.
Parameters:
space - ANOI space object.
rootNSUid - UID for the base trie. This is the namespace of the type
and time systems.
Attributes created:
space - This is the same as all other ANOI objects - a reference to
the ANOI space that hosts the ANOIki.
rootTrie - This is the root name space, used to name site wide
attributes.
namespace - This is the trie for article names. By default this
coincides with the root name space.
"""
assert type(rootNSUid) == types.IntType
self.space = space
rootNS = ANOITrie.getNamespace(space, rootNSUid)
articleNSUid = rootNS.getName("ANOIkiArticleRoot")
if articleNSUid == ANOIBaseSpace.NIL:
articleNSUid = self.space.getUid()
rootNS.setName("ANOIkiArticleRoot", articleNSUid)
self.namespace = ANOITrie.ANOITrie(space, articleNSUid, rootNS)
urlNSUid = rootNS.getName("ANOIkiUrlRoot")
if urlNSUid == ANOIBaseSpace.NIL:
urlNSUid = self.space.getUid()
rootNS.setName("ANOIkiUrlRoot", urlNSUid)
self.urlNS = ANOITrie.ANOITrie(space, urlNSUid ,rootNS)
self.root = self.namespace.root
self.rootTrie = self.namespace.baseTrie
self.typeSystem = ANOITypes.ANOITypeManager(space, rootNS)
self.timeSystem = ANOITime.ANOITimeManager(space, rootNS, "TIME")
self.createdTimeSystem = ANOITime.ANOITimeManager(space, rootNS,
"CREATED")
# Get property keys
self.titleAttr = self.typeSystem.getOrCreatePropertyKey("TITLE")
self.authorAttr = self.typeSystem.getOrCreatePropertyKey("AUTHOR")
# Get (or set) type stuff
self.articleType = self.rootTrie.getName("ANOIkiArticleType")
if self.articleType == ANOIBaseSpace.NIL:
atomType = self.typeSystem.atomType
aTypeUid = self.typeSystem.createType("ANOIkiArticleType",
{self.titleAttr : atomType,
self.authorAttr : None})
self.articleType = aTypeUid
self.placeHolderType = self.rootTrie.getName("ANOIkiPlaceHolderType")
if self.placeHolderType == ANOIBaseSpace.NIL:
phTypeUid = self.typeSystem.createType("ANOIkiPlaceHolderType")
self.placeHolderType = phTypeUid
self.externalLinkType = self.rootTrie.getName("ANOIkiExtLinkType")
if self.externalLinkType == ANOIBaseSpace.NIL:
extLinkTypeUid = self.typeSystem.createType("ANOIkiExtLinkType")
self.externalLinkType = extLinkTypeUid
# ____________________________________________________________
def isArticle (self, uid):
"""ANOIkiFrontEnd.isArticle()
"""
return self.typeSystem.getType(uid) == self.articleType
# ____________________________________________________________
def makeTitle (self, uid, title, authorName = None, authorUid = None):
"""ANOIkiFrontEnd.makeTitle()
Force a UID to be of ANOIkiArticleType.
"""
namedUid = self.namespace.getName(title)
if namedUid == ANOIBaseSpace.NIL:
self.namespace.setName(title, uid)
elif namedUid != uid:
raise ANOIBaseSpace.ANOIError, ("Title already maps to a UID "
"(got %d, expected %d)." %
(namedUid, uid))
# Reassign type
self.space.crossEquals(uid, self.typeSystem.typeAttr, self.articleType)
self.space.crossEquals(uid, self.authorAttr, ANOIBaseSpace.NIL)
self.space.crossEquals(uid, self.titleAttr, ANOIBaseSpace.NIL)
self.setArticleAttributes(uid, title, authorName, authorUid)
# ____________________________________________________________
def addTitle (self, title, authorName = None, authorUid = None):
"""ANOIkiFrontEnd.addTitle()
In some sense this has become the constructor for the
ANOIkiArticleType.
"""
retVal = ANOIBaseSpace.NIL
if self.namespace.hasName(title):
uid = self.namespace.getName(title)
raise ANOIBaseSpace.ANOIError, ("Title already maps to a UID "
"(%d)." % (uid,))
else:
# Article UID
uid = self.typeSystem.createInstance(self.articleType)
self.namespace.setName(title, uid)
self.setArticleAttributes(uid, title, authorName, authorUid)
# Good to go...
retVal = uid
return retVal
# ____________________________________________________________
def setArticleAttributes (self, uid, title, authorName = None,
authorUid = None):
"""ANOIkiFrontEnd.setArticleAttributes()
"""
# Title UID
titleUid = self.typeSystem.createInstance(self.typeSystem.atomType)
self.space.setUidContent(titleUid, map(ord, list(title)))
self.space.crossEquals(uid, self.titleAttr, titleUid)
# Author UID
if authorUid != None:
self.space.crossEquals(uid, self.authorAttr, authorUid)
elif authorName != None:
if not self.namespace.hasName(authorName):
authorUid = self.addTitle(authorName)
self.space.crossEquals(authorUid, self.authorAttr,
authorUid)
else:
authorUid = self.namespace.getName(authorName)
self.space.crossEquals(uid, self.authorAttr, authorUid)
# Times
self.createdTimeSystem.timeStamp(uid)
self.timeSystem.timeStamp(uid)
# ____________________________________________________________
def getTitleText (self, uid):
"""ANOIkiFrontEnd.getTitleText()
Return the title associated with a UID, if it exists. If no title
mapping is found, returns None.
"""
retVal = None
if self.isArticle(uid):
titleAtom = self.space.cross(uid, self.titleAttr)
if titleAtom != ANOIBaseSpace.NIL:
titleVec = self.space.getUidContent(titleAtom)
retVal = string.join(map(chr, titleVec), "")
return retVal
# ____________________________________________________________
def updateContent (self, uid, contentStr = None):
"""ANOIkiFrontEnd.updateContent()
"""
contentVec = self.space.getUidContent(uid)
if contentStr == None:
contentStr = self.decompressArticle(uid)
articleVec = self.compressArticle(contentStr)
if articleVec != contentVec:
self.space.setUidContent(uid, articleVec)
self.timeSystem.timeStamp(uid)
# ____________________________________________________________
def getUrlUid (self, urlStr):
"""ANOIkiFrontEnd.getUrlUid()
"""
retVal = self.urlNS.getName(urlStr)
if retVal == ANOIBaseSpace.NIL:
retVal = self.typeSystem.createInstance(self.externalLinkType)
if retVal != ANOIBaseSpace.NIL:
self.space.setUidContent(retVal, map(ord, list(urlStr)))
self.urlNS.setName(urlStr, retVal)
return retVal
# ____________________________________________________________
def getWikiUid (self, wikiStr):
"""ANOIkiFrontEnd.getWikiUid()
"""
retVal = self.namespace.getName(wikiStr)
if retVal == ANOIBaseSpace.NIL:
retVal = self.typeSystem.createInstance(self.placeHolderType)
if retVal != ANOIBaseSpace.NIL:
self.space.setUidContent(retVal, map(ord, list(wikiStr)))
self.namespace.setName(wikiStr, retVal)
return retVal
# ____________________________________________________________
def compressArticle (self, content):
"""ANOIkiFrontEnd.compressArticle()
"""
global urlReObj, wikiReObj, uidReObj, reObj
# Suck out all the external references.
contentList = list(content)
matchIter = reObj.finditer(content)
offset = 0
for matchObj in matchIter:
replaceUid = None
matchStr = matchObj.groups()[0]
if urlReObj.match(matchStr):
replaceUid = self.getUrlUid(matchStr)
elif wikiReObj.match(matchStr):
replaceUid = self.getWikiUid(matchStr)
else:
try:
replaceUid = int(matchStr[3:])
except:
replaceUid = None
if replaceUid:
start = matchObj.start()
end = matchObj.end()
contentList[start + offset: end + offset] = [replaceUid]
offset -= (end - start) - 1
def toUid (c):
if type(c) == str:
return ord(c)
return c
contentVec = map(toUid, contentList)
return self.namespace.compressVector(contentVec)
# ____________________________________________________________
def decompressArticle (self, uid):
"""ANOIkiFrontEnd.decompressArticle()
Outputs editable 7-bit ASCII text.
"""
global CHAR_MAX
retVec = []
cross = self.space.cross
contentVec = self.space.getUidContent(uid)
for contentUid in contentVec:
if contentUid < CHAR_MAX: # XXX - Dirty shameful hack.
retVec.append(chr(contentUid))
else:
titleUid = cross(contentUid, self.titleAttr)
if titleUid != ANOIBaseSpace.NIL:
titleVec = self.space.getUidContent(titleUid)
titleText = string.join(map(chr, titleVec), "")
else:
titleText = "Uid%d" % contentUid
retVec.append(titleText)
return string.join(retVec, "")
# ____________________________________________________________
def htmlizeArticle (self, uid, baseURL):
"""ANOIkiFrontEnd.htmlizeArticle()
Outputs HTML formatted article.
"""
global CHAR_MAX
retVec = []
cross = self.space.cross
contentVec = self.space.getUidContent(uid)
for contentUid in contentVec:
typeUid = self.typeSystem.getType(contentUid)
text = None
if typeUid == self.articleType:
titleUid = cross(contentUid, self.titleAttr)
if titleUid != ANOIBaseSpace.NIL:
titleVec = self.space.getUidContent(titleUid)
text = string.join(map(chr, titleVec), "")
else:
text = "Uid%d" % contentUid
retVec.append("<a href=\"%s?uid=%d\" class=\"article\">%s</a>"
% (baseURL, contentUid, text))
elif typeUid == self.placeHolderType:
holderVec = self.space.getUidContent(contentUid)
holderStr = ANOIUtil.uidVecToStr(holderVec)
# FIXME: The generated URL introduces additional coupling
# between the anoi.cgi and the ANOIkiFrontEnd.
# i.e. the CGI needs to support not just the uid parameter,
# but the action parameter and corresponding value.
retVec.append('<a href="%s?uid=%d;action=create" '
'class="placeholder">%s'
'<sup>?</sup></a>' % (baseURL, contentUid,
holderStr))
elif typeUid == self.externalLinkType:
urlVec = self.space.getUidContent(contentUid)
urlStr = ANOIUtil.uidVecToStr(urlVec)
retVec.append("<a href=\"%s\" class=\"external\">%s</a>" %
(urlStr, urlStr))
else:
if contentUid < CHAR_MAX:
contentChr = chr(contentUid)
if contentChr in (string.letters + string.digits):
text = contentChr
elif contentChr == '\n':
text = "<br/>"
elif contentChr == ' ':
text = " "
elif contentChr == '\t':
text = " "
else:
text = "&#%d;" % contentUid
else:
text = "Uid%d" % contentUid
retVec.append("<a href=\"%s?uid=%d\" class=\"internal\">%s</a>"
% (baseURL, contentUid, text))
return '<p>%s</p>' % string.join(retVec, "")
# ____________________________________________________________
def generateHtmlIndex (self, baseURL):
"""ANOIkiFrontEnd.generateHtmlIndex()
"""
retVec = ["<ul>"]
titleMap = self.namespace.getVectors()
titles = titleMap.items()
titles.sort()
for (titleVec, refUid) in titles:
title = string.join(map(chr, titleVec), "")
if self.typeSystem.getType(refUid) == self.placeHolderType:
pass
else:
retVec.append("<li><a href=\"%s?uid=%d\">%s</a></li>" %
(baseURL, refUid, title))
retVec.append("</ul>")
return string.join(retVec, "\r\n")
# ____________________________________________________________
def generateChanges (self, baseURL, maxCount = 20):
"""ANOIkiFrontEnd.generateChanges()
"""
retList = []
timeDim = self.timeSystem.timeDim
neg = timeDim.negUid
ref = timeDim.refUid
count = 0
listItemFmtStr = "<li>%s : <a href=\"%s?uid=%d\">%s</a></li>"
crnt = self.space.cross(timeDim.uid, timeDim.posUid)
while (crnt != ANOIBaseSpace.NIL) and (count < maxCount):
referencedUid = self.space.cross(crnt, ref)
if referencedUid != ANOIBaseSpace.NIL:
titleUid = self.space.cross(referencedUid, self.titleAttr)
if titleUid != ANOIBaseSpace.NIL:
titleVec = self.space.getUidContent(titleUid)
titleStr = ANOIUtil.uidVecToStr(titleVec)
timeVec = self.space.getUidContent(crnt)
timeStr = ANOIUtil.uidVecToStr(timeVec)
retList.append(listItemFmtStr % (timeStr, baseURL,
referencedUid, titleStr))
count += 1
crnt = self.space.cross(crnt, neg)
return "<ul>%s</ul>" % (string.join(retList, "\r\n"))
# ______________________________________________________________________
# End of ANOIki.py