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.You are not allowed to attach a file to this page.