Attachment 'VisualSiteMap_1.3.1-BABY.py'

Download

   1 """
   2     MoinMoin - VisualSiteMap action
   3 
   4     Idea is based on the webdot.py action.
   5     
   6     More or less redid it from scratch. Differs from the webdot action in several ways:
   7     * Uses the dot executable, not webdot, since webdot is not available on windows.
   8     * All links up to the search depth are displayed.
   9     * There's no maximal limit to the displayed nodes.
  10     * Nodes are marked during depth first visit, so each node is visited only once.
  11     * The visit method in class LocalSiteMap gets the whole tree as parameter.
  12       That way additional treenode information may be shown in the graph.
  13     * All edges between nodes contained in the graph are displayed, even if MAX_DEPTH is exceeded that way.
  14     * Optional depth controls
  15     * Nodes linked more then STRONG_LINK_NR times are highlighted using the STRONG_COLOR.
  16     * Search depth is configurable.
  17     
  18     Add this to your stylesheet:
  19     img.sitemap
  20     {
  21       border-width: 1;
  22       border-color: #000000;
  23     }
  24 
  25     07.10.2004
  26     * Maximum image size can be configured
  27     * Output image format is configurable
  28     * David Linke changed the output code (print() -> request.write())
  29     * Changed link counting algorithm to get the depth controls right.
  30 
  31     08.10.2004
  32     * IE caching problem with depth controls resolved. Now the current search depth is part of the file names.
  33     * Problems with pagenames containing non ASCII characters fixed.
  34     
  35     14.03.2005
  36     * cleanup & adapted to moin 1.3.4 -- ThomasWaldmann
  37     * Fixed for utf-8 and sub pages
  38 
  39     16.3.2005
  40     * included patch from David Linke for Windows compatibility
  41     * FONTNAME and FONTSIZE
  42     * removed invalid print debug statements
  43     * use config.charset
  44     
  45 """
  46 
  47 ##################################################################
  48 # Be warned that calculating large graphs may block your server! #   
  49 # So be careful with the parameter settings.                     #
  50 ##################################################################
  51 
  52 # This should be a public path on your web server. The dot files, images and map files are created in this directory and
  53 # served from there.
  54 #CACHE_DIR  = "C:/DocumentRoot/cache"
  55 # NOTE: If you use a 'relative' path for CACHE_DIR, it will be relative to where moin.cgi is
  56 #CACHE_DIR  = "wiki/cache"
  57 #CACHE_URL  = "http://my-server/cache"
  58 # NOTE: if you don't use 'http://name-of-server', the path will be absolute within the same
  59 #       server name as the moin instance you're using
  60 #CACHE_URL  = "/wiki/cache"
  61 CACHE_DIR  = "/org/de.wikiwikiweb.moinmaster/htdocs/cache"
  62 CACHE_URL  = "http://moinmaster.wikiwikiweb.de/wiki/cache"
  63 
  64 # Absolute location of the dot (or neato) executable.
  65 #DOT_EXE    = "C:/Programme/ATT/GraphViz/bin/dot.exe"
  66 #DOT_EXE    = "/usr/bin/dot"
  67 DOT_EXE    = "/usr/bin/neato"
  68 
  69 # Categories are filtered in some way.
  70 CATEGORY_STRING = "^Category"
  71 
  72 # Graph controls.
  73 DEFAULT_DEPTH = 2
  74 STRONG_LINK_NR = 4
  75 
  76 # Optional controls for interactive modification of the search depth.
  77 DEPTH_CONTROL = False
  78 MAX_DEPTH  = 4
  79 
  80 # Desired image format (eg. png, jpg, gif - see the dot documentation)
  81 OUTPUT_FORMAT = "png"
  82 
  83 # Maximum output size in inches. Set to None to disable size limitation,
  84 # then the graph is made as big as needed (best for readability).
  85 # OUTPUT_SIZE="8,12" sets maximum width to 8, maximum height to 12 inches.
  86 OUTPUT_SIZE = None
  87 
  88 # Name and Size of the font use
  89 # Times, Helvetica, Courier, Symbol are supported on any platform.
  90 # Others may NOT be supported.
  91 # When selecting a font, make sure it support unicode chars (at least the
  92 # ones you use, e.g. german umlauts or french accented chars).
  93 FONTNAME = "Times"
  94 FONTSIZE = "10"
  95 
  96 # Colors of boxes and edges.
  97 BOX_COLOR = "#E0F0FF"
  98 ROOT_COLOR = "#FFE0E0"
  99 STRONG_COLOR = "#E0FFE0"
 100 EDGE_COLOR = "#888888"
 101 
 102 import re, os
 103 from MoinMoin import config, wikiutil
 104 from MoinMoin.Page import Page
 105 
 106 class LocalSiteMap:
 107     def __init__(self, name, maxdepth):
 108         self.name = name
 109         self.maxdepth = maxdepth
 110         self.result = []
 111 
 112     def output(self, request):
 113         pagebuilder = GraphBuilder(request, self.maxdepth)
 114         root = pagebuilder.build_graph(self.name)
 115         # count links
 116         for edge in pagebuilder.all_edges:
 117             edge[0].linkedfrom += 1
 118             edge[1].linkedto += 1
 119         # write nodes
 120         for node in pagebuilder.all_nodes:
 121             self.append('  "%s"'% node.name)
 122             if node.depth > 0:
 123                 if node.linkedto >= STRONG_LINK_NR:
 124                     self.append('  [label="%s",color="%s"];\n' % (node.name, STRONG_COLOR))
 125                 else:
 126                     self.append('  [label="%s"];\n' % (node.name))
 127             else:
 128                 self.append('[label="%s",shape=box,style=filled,color="%s"];\n' % (node.name, ROOT_COLOR))
 129         # write edges
 130         for edge in pagebuilder.all_edges:
 131             self.append('  "%s"->"%s";\n' % (edge[0].name, edge[1].name))
 132             
 133         return ''.join(self.result)
 134 
 135     def append(self, text):
 136         self.result.append(text)
 137 
 138 
 139 class GraphBuilder:
 140     def __init__(self, request, maxdepth):
 141         self.request = request
 142         self.maxdepth = maxdepth
 143         self.all_nodes = []
 144         self.all_edges = []
 145         
 146     def is_ok(self, child):
 147         if not self.request.user.may.read(child):
 148             return 0
 149         if Page(self.request, child).exists() and not re.search(r'%s' % CATEGORY_STRING, child):
 150             return 1
 151         return 0
 152 
 153     def build_graph(self, name):
 154         # Reuse generated trees
 155         nodesMap = {}
 156         root = Node(name)
 157         nodesMap[name] = root
 158         root.visited = 1
 159         self.all_nodes.append(root)
 160         self.recurse_build([root], 1, nodesMap)
 161         return root
 162 
 163     def recurse_build(self, nodes, depth, nodesMap):
 164         # collect all nodes of the current search depth here for the next recursion step
 165         child_nodes = []
 166         # iterate over the nodes
 167         for node in nodes:
 168             for child in Page(self.request, node.name).getPageLinks(self.request):            
 169                 if self.is_ok(child):
 170                     # Create the node with the given name
 171                     if not nodesMap.has_key(child):
 172                         # create the new node and store it
 173                         newNode = Node(child)
 174                         newNode.depth = depth
 175                     else:
 176                         newNode = nodesMap[child]
 177                     # If the current depth doesn't exceed the maximum depth, add newNode to recursion step
 178                     if depth <= self.maxdepth:
 179                         # The node is appended to the nodes list for the next recursion step.
 180                         nodesMap[child] = newNode
 181                         self.all_nodes.append(newNode)
 182                         child_nodes.append(newNode)
 183                         node.append(newNode)
 184                         # Draw an edge.
 185                         edge = (node, newNode)
 186                         if not edge in self.all_edges:
 187                             self.all_edges.append(edge)
 188         # recurse, if the current recursion step yields children
 189         if len(child_nodes):
 190             self.recurse_build(child_nodes, depth+1, nodesMap)
 191 
 192 class Node:
 193     def __init__(self, name):
 194         self.name = name
 195         self.children = []
 196         self.visited = 0
 197         self.linkedfrom = 0
 198         self.linkedto = 0
 199         self.depth = 0
 200         
 201     def append(self, node):
 202         self.children.append(node)
 203 
 204 def execute(pagename, request):
 205     _ = request.getText
 206     
 207     maxdepth = DEFAULT_DEPTH
 208     if DEPTH_CONTROL and request.form.has_key('depth'):
 209         maxdepth = int(request.form['depth'][0])
 210     
 211     if maxdepth > MAX_DEPTH:
 212         maxdepth = MAX_DEPTH
 213       
 214     request.http_headers()
 215     wikiutil.send_title(request, _('Visual Map of %s') % pagename, pagename=pagename)
 216 
 217     baseurl = request.getBaseURL()
 218 
 219     wikinamefs = wikiutil.quoteWikinameFS(pagename)
 220     wikinameurl = wikiutil.quoteWikinameURL(wikinamefs)
 221     fnprefix = os.path.join(CACHE_DIR, '%s_%s' % (wikinamefs, maxdepth))
 222     dotfilename = '%s.%s' % (fnprefix, 'dot')
 223     imagefilename = '%s.%s' % (fnprefix, OUTPUT_FORMAT)
 224     mapfilename = '%s.%s' % (fnprefix, 'cmap')
 225     imageurl = '%s/%s_%s.%s' % (CACHE_URL, wikinameurl, maxdepth, OUTPUT_FORMAT)
 226 
 227     lsm = LocalSiteMap(pagename, maxdepth).output(request).encode(config.charset)
 228     
 229     os.umask(022)
 230     dotfile = file(dotfilename, 'w')
 231     dotfile.write('digraph G {\n')
 232     if OUTPUT_SIZE:
 233         dotfile.write('  size="%s"\n' % OUTPUT_SIZE)
 234         dotfile.write('  ratio=compress;\n')
 235     dotfile.write('  URL="%s";\n' % wikinameurl)
 236     dotfile.write('  overlap=false;\n')
 237     dotfile.write('  concentrate=true;\n')
 238     dotfile.write('  edge [color="%s"];\n' % EDGE_COLOR)
 239     dotfile.write('  node [URL="%s/\N", ' % baseurl)
 240     dotfile.write('fontcolor=black, fontname="%s", fontsize=%s, style=filled, color="%s"]\n' % (FONTNAME, FONTSIZE, BOX_COLOR))
 241     dotfile.write(lsm)
 242     dotfile.write('}\n')
 243     dotfile.close()
 244     
 245     os.system('%s -T%s -o"%s" "%s"' % (DOT_EXE, OUTPUT_FORMAT, imagefilename, dotfilename))
 246     os.system('%s -Tcmap -o"%s" "%s"' % (DOT_EXE, mapfilename, dotfilename))
 247    
 248     request.write('<center><img class="sitemap" border="1" src="%s" usemap="#map1"></center>' % (imageurl,))
 249     request.write('<map name="map1">')
 250     mapfile = file(mapfilename, 'r')
 251     for row in mapfile:
 252         request.write(row)
 253     mapfile.close()
 254     request.write('</map>')
 255     
 256     if DEPTH_CONTROL:
 257         linkname = wikiutil.quoteWikinameURL(pagename)
 258         request.write('<p align="center">')
 259         if maxdepth > 1:
 260             request.write('<a href="%s/%s?action=VisualSiteMap&depth=%s">Less</a>' % (baseurl, linkname, maxdepth-1))
 261         else:
 262             request.write('Less')
 263         request.write(' | ')
 264 
 265         if maxdepth < MAX_DEPTH:
 266             request.write('<a href="%s/%s?action=VisualSiteMap&depth=%s">More</a>' % (baseurl, linkname, maxdepth+1))
 267         else:
 268             request.write('More')
 269         request.write('</p>')
 270       
 271     request.write('<p align="center"><small>Search depth is %s. Nodes linked more than %s times are highlighted.</small></p>' % (maxdepth, STRONG_LINK_NR))
 272 
 273     wikiutil.send_footer(request, pagename)

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2007-05-27 12:55:24, 10.1 KB) [[attachment:VisualSiteMap_1.3.1-BABY.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.