Attachment 'VisualSiteMap_1.9.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 the following line to your (apache?) webserver configuration:
19 Alias /moin_cache/ /var/cache/moin/
20 See CACHE_URL and CACHE_DIR below.
21
22 Add this to your stylesheet:
23 img.sitemap
24 {
25 border-width: 1;
26 border-color: #000000;
27 }
28
29 The following settings may be worth a change:
30 * DEPTH_CONTROL
31 * OUTPUT_FORMAT
32 * DEFAULT_DEPTH
33 * MAX_DEPTH
34 * LINK_TO_SITEMAP
35
36 07.10.2004
37 * Maximum image size can be configured
38 * Output image format is configurable
39 * David Linke changed the output code (print() -> request.write())
40 * Changed link counting algorithm to get the depth controls right.
41
42 08.10.2004
43 * IE caching problem with depth controls resolved. Now the current search depth is part of the file names.
44 * Problems with pagenames containing non ASCII characters fixed.
45
46 14.03.2005
47 * cleanup & adapted to moin 1.3.4 -- ThomasWaldmann
48 * Fixed for utf-8 and sub pages
49
50 16.3.2005
51 * included patch from David Linke for Windows compatibility
52 * FONTNAME and FONTSIZE
53 * removed invalid print debug statements
54 * use config.charset
55
56 19.08.2014
57 * Cleanup & adapted fto moin 1.9.4
58 * Changed default output type from PNG to SVG.
59 * Use configured category regex instead of separate setting.
60 * Configurable: node links point to the VisualSiteMap of the target node (instead of its wiki page).
61 * Fixed FS/URL escaping inconsistency for pagenames with special characters.
62 * Enabled DEPTH_CONTROL by default.
63
64 """
65
66 ##################################################################
67 # Be warned that calculating large graphs may block your server! #
68 # So be careful with the parameter settings. #
69 ##################################################################
70
71 # This should be a public path on your web server. The dot files, images and map files are created in this directory and
72 # served from there.
73 #CACHE_DIR = "C:/DocumentRoot/cache"
74 #CACHE_URL = "http://my-server/cache"
75 CACHE_DIR = "/var/cache/moin/"
76 CACHE_URL = "/moin_cache"
77
78 # Absolute location of the dot (or neato) executable.
79 #DOT_EXE = "C:/Programme/ATT/GraphViz/bin/dot.exe"
80 #DOT_EXE = "/usr/bin/dot"
81 DOT_EXE = "/usr/bin/neato"
82
83 # Graph controls.
84 DEFAULT_DEPTH = 2
85 STRONG_LINK_NR = 4
86
87 # nodes are linked their sitemap (instead of their wiki page)
88 LINK_TO_SITEMAP = True
89
90 # Optional controls for interactive modification of the search depth.
91 DEPTH_CONTROL = True
92 MAX_DEPTH = 4
93
94 # Desired image format (eg. png, jpg, gif - see the dot documentation)
95 OUTPUT_FORMAT = "svg"
96
97 # Maximum output size in inches. Set to None to disable size limitation,
98 # then the graph is made as big as needed (best for readability).
99 # OUTPUT_SIZE="8,12" sets maximum width to 8, maximum height to 12 inches.
100 OUTPUT_SIZE = None
101
102 # Name and Size of the font use
103 # Times, Helvetica, Courier, Symbol are supported on any platform.
104 # Others may NOT be supported.
105 # When selecting a font, make sure it support unicode chars (at least the
106 # ones you use, e.g. german umlauts or french accented chars).
107 FONTNAME = "Times"
108 FONTSIZE = "10"
109
110 # Colors of boxes and edges.
111 BOX_COLOR = "#E0F0FF"
112 ROOT_COLOR = "#FFE0E0"
113 STRONG_COLOR = "#E0FFE0"
114 EDGE_COLOR = "#888888"
115
116
117 import re
118 import os
119 import subprocess
120
121 from MoinMoin import config, wikiutil
122 from MoinMoin.Page import Page
123
124
125 class LocalSiteMap:
126 def __init__(self, name, maxdepth):
127 self.name = name
128 self.maxdepth = maxdepth
129 self.result = []
130
131 def output(self, request):
132 pagebuilder = GraphBuilder(request, self.maxdepth)
133 root = pagebuilder.build_graph(self.name)
134 # count links
135 for edge in pagebuilder.all_edges:
136 edge[0].linkedfrom += 1
137 edge[1].linkedto += 1
138 # write nodes
139 for node in pagebuilder.all_nodes:
140 self.append(' "%s"'% node.name)
141 if node.depth > 0:
142 if node.linkedto >= STRONG_LINK_NR:
143 self.append(' [label="%s",color="%s"];\n' % (node.name, STRONG_COLOR))
144 else:
145 self.append(' [label="%s"];\n' % (node.name))
146 else:
147 self.append('[label="%s",shape=box,style=filled,color="%s"];\n' % (node.name, ROOT_COLOR))
148 # write edges
149 for edge in pagebuilder.all_edges:
150 self.append(' "%s"->"%s";\n' % (edge[0].name, edge[1].name))
151
152 return ''.join(self.result)
153
154 def append(self, text):
155 self.result.append(text)
156
157
158 class GraphBuilder:
159 def __init__(self, request, maxdepth):
160 self.request = request
161 self.maxdepth = maxdepth
162 self.all_nodes = []
163 self.all_edges = []
164
165 def is_ok(self, child):
166 if not self.request.user.may.read(child):
167 return 0
168 if Page(self.request, child).exists() and not re.search(self.request.cfg.page_category_regex, child):
169 return 1
170 return 0
171
172 def build_graph(self, name):
173 # Reuse generated trees
174 nodesMap = {}
175 root = Node(name)
176 nodesMap[name] = root
177 root.visited = 1
178 self.all_nodes.append(root)
179 self.recurse_build([root], 1, nodesMap)
180 return root
181
182 def recurse_build(self, nodes, depth, nodesMap):
183 # collect all nodes of the current search depth here for the next recursion step
184 child_nodes = []
185 # iterate over the nodes
186 for node in nodes:
187 for child in Page(self.request, node.name).getPageLinks(self.request):
188 if self.is_ok(child):
189 # Create the node with the given name
190 if not nodesMap.has_key(child):
191 # create the new node and store it
192 newNode = Node(child)
193 newNode.depth = depth
194 else:
195 newNode = nodesMap[child]
196 # If the current depth doesn't exceed the maximum depth, add newNode to recursion step
197 if depth <= self.maxdepth:
198 # The node is appended to the nodes list for the next recursion step.
199 nodesMap[child] = newNode
200 self.all_nodes.append(newNode)
201 child_nodes.append(newNode)
202 node.append(newNode)
203 # Draw an edge.
204 edge = (node, newNode)
205 if not edge in self.all_edges:
206 self.all_edges.append(edge)
207 # recurse, if the current recursion step yields children
208 if len(child_nodes):
209 self.recurse_build(child_nodes, depth+1, nodesMap)
210
211
212 class Node:
213 def __init__(self, name):
214 self.name = name
215 self.children = []
216 self.visited = 0
217 self.linkedfrom = 0
218 self.linkedto = 0
219 self.depth = 0
220
221 def append(self, node):
222 self.children.append(node)
223
224
225 def execute(pagename, request):
226 _ = request.getText
227
228 maxdepth = DEFAULT_DEPTH
229 if DEPTH_CONTROL and request.values.has_key('depth'):
230 maxdepth = int(request.values['depth'][0])
231
232 if maxdepth > MAX_DEPTH:
233 maxdepth = MAX_DEPTH
234
235 baseurl = request.getBaseURL().rstrip("/")
236 def get_page_link(pname, to_sitemap=LINK_TO_SITEMAP, **kwargs):
237 if pname is None:
238 pagelinkname = r'\N'
239 else:
240 pagelinkname = wikiutil.quoteWikinameURL(pname)
241 if to_sitemap:
242 link = "%s/%s?action=VisualSiteMap" % (baseurl, pagelinkname)
243 for key, value in kwargs.iteritems():
244 link += "&%s=%s" % (key, value)
245 else:
246 link = "%s/%s" % (baseurl, pagelinkname)
247 return link
248
249 request.theme.send_title(_('Visual Map of %s') % pagename, pagename=pagename)
250
251 wikinamefs = wikiutil.quoteWikinameFS(pagename)
252 fnprefix = os.path.join(CACHE_DIR, '%s_%s' % (wikinamefs, maxdepth))
253 dotfilename = '%s.%s' % (fnprefix, 'dot')
254 imagefilename = '%s.%s' % (fnprefix, OUTPUT_FORMAT)
255 mapfilename = '%s.%s' % (fnprefix, 'cmap')
256 imageurl = '%s/%s_%s.%s' % (CACHE_URL, wikinamefs, maxdepth, OUTPUT_FORMAT)
257
258 lsm = LocalSiteMap(pagename, maxdepth).output(request).encode(config.charset)
259
260 os.umask(022)
261 dotfile = file(dotfilename, 'w')
262 dotfile.write('digraph G {\n')
263 if OUTPUT_SIZE:
264 dotfile.write(' size="%s"\n' % OUTPUT_SIZE)
265 dotfile.write(' ratio=compress;\n')
266 dotfile.write(' URL="%s";\n' % get_page_link(pagename, to_sitemap=False))
267 dotfile.write(' overlap=false;\n')
268 dotfile.write(' concentrate=true;\n')
269 dotfile.write(' edge [color="%s"];\n' % EDGE_COLOR)
270 dotfile.write(' node [URL="%s", ' % get_page_link(None, depth=maxdepth))
271 dotfile.write('fontcolor=black, fontname="%s", fontsize=%s, style=filled, color="%s"]\n' % (FONTNAME, FONTSIZE, BOX_COLOR))
272 dotfile.write(lsm)
273 dotfile.write('}\n')
274 dotfile.close()
275
276 subprocess.call([DOT_EXE, "-T%s" % OUTPUT_FORMAT, "-o%s" % imagefilename, dotfilename])
277 subprocess.call([DOT_EXE, "-Tcmap", "-o%s" % mapfilename, dotfilename])
278
279 # show the image
280 request.write('<center><img class="sitemap" border="1" src="%s" usemap="#map1"/></center>' % imageurl)
281
282 # image map for links ("img" does not enable svg links)
283 request.write('<map name="map1">')
284 mapfile = file(mapfilename, 'r')
285 for row in mapfile:
286 request.write(row)
287 mapfile.close()
288 request.write('</map>')
289
290 if DEPTH_CONTROL:
291 linkname = wikiutil.quoteWikinameURL(pagename)
292 links = []
293 if maxdepth > 1:
294 links.append('<a href="%s">Less</a>' % get_page_link(pagename, depth=maxdepth-1))
295 if maxdepth < MAX_DEPTH:
296 links.append('<a href="%s">More</a>' % get_page_link(pagename, depth=maxdepth+1))
297 request.write('<p align="center">%s</p>' % ' | '.join(links))
298
299 request.write('<p align="center"><small>Search depth is %s. Nodes linked more than %s times are highlighted.</small></p>' % (maxdepth, STRONG_LINK_NR))
300
301 request.theme.send_footer(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.