Attachment 'VisualSiteMap_1.3.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 #CACHE_URL = "http://my-server/cache"
56 CACHE_DIR = "/org/de.wikiwikiweb.moinmaster/htdocs/cache"
57 CACHE_URL = "http://moinmaster.wikiwikiweb.de/wiki/cache"
58
59 # Absolute location of the dot (or neato) executable.
60 #DOT_EXE = "C:/Programme/ATT/GraphViz/bin/dot.exe"
61 #DOT_EXE = "/usr/bin/dot"
62 DOT_EXE = "/usr/bin/neato"
63
64 # Categories are filtered in some way.
65 CATEGORY_STRING = "^Kategorie"
66
67 # Graph controls.
68 DEFAULT_DEPTH = 2
69 STRONG_LINK_NR = 4
70
71 # Optional controls for interactive modification of the search depth.
72 DEPTH_CONTROL = False
73 MAX_DEPTH = 4
74
75 # Desired image format (eg. png, jpg, gif - see the dot documentation)
76 OUTPUT_FORMAT = "png"
77
78 # Maximum output size in inches. Set to None to disable size limitation,
79 # then the graph is made as big as needed (best for readability).
80 # OUTPUT_SIZE="8,12" sets maximum width to 8, maximum height to 12 inches.
81 OUTPUT_SIZE = None
82
83 # Name and Size of the font use
84 # Times, Helvetica, Courier, Symbol are supported on any platform.
85 # Others may NOT be supported.
86 # When selecting a font, make sure it support unicode chars (at least the
87 # ones you use, e.g. german umlauts or french accented chars).
88 FONTNAME = "Times"
89 FONTSIZE = "10"
90
91 # Colors of boxes and edges.
92 BOX_COLOR = "#E0F0FF"
93 ROOT_COLOR = "#FFE0E0"
94 STRONG_COLOR = "#E0FFE0"
95 EDGE_COLOR = "#888888"
96
97 import re, os
98 from MoinMoin import config, wikiutil
99 from MoinMoin.Page import Page
100
101 class LocalSiteMap:
102 def __init__(self, name, maxdepth):
103 self.name = name
104 self.maxdepth = maxdepth
105 self.result = []
106
107 def output(self, request):
108 pagebuilder = GraphBuilder(request, self.maxdepth)
109 root = pagebuilder.build_graph(self.name)
110 # count links
111 for edge in pagebuilder.all_edges:
112 edge[0].linkedfrom += 1
113 edge[1].linkedto += 1
114 # write nodes
115 for node in pagebuilder.all_nodes:
116 self.append(' "%s"'% node.name)
117 if node.depth > 0:
118 if node.linkedto >= STRONG_LINK_NR:
119 self.append(' [label="%s",color="%s"];\n' % (node.name, STRONG_COLOR))
120 else:
121 self.append(' [label="%s"];\n' % (node.name))
122 else:
123 self.append('[label="%s",shape=box,style=filled,color="%s"];\n' % (node.name, ROOT_COLOR))
124 # write edges
125 for edge in pagebuilder.all_edges:
126 self.append(' "%s"->"%s";\n' % (edge[0].name, edge[1].name))
127
128 return ''.join(self.result)
129
130 def append(self, text):
131 self.result.append(text)
132
133
134 class GraphBuilder:
135 def __init__(self, request, maxdepth):
136 self.request = request
137 self.maxdepth = maxdepth
138 self.all_nodes = []
139 self.all_edges = []
140
141 def is_ok(self, child):
142 if not self.request.user.may.read(child):
143 return 0
144 if Page(self.request, child).exists() and not re.search(r'%s' % CATEGORY_STRING, child):
145 return 1
146 return 0
147
148 def build_graph(self, name):
149 # Reuse generated trees
150 nodesMap = {}
151 root = Node(name)
152 nodesMap[name] = root
153 root.visited = 1
154 self.all_nodes.append(root)
155 self.recurse_build([root], 1, nodesMap)
156 return root
157
158 def recurse_build(self, nodes, depth, nodesMap):
159 # collect all nodes of the current search depth here for the next recursion step
160 child_nodes = []
161 # iterate over the nodes
162 for node in nodes:
163 for child in Page(self.request, node.name).getPageLinks(self.request):
164 if self.is_ok(child):
165 # Create the node with the given name
166 if not nodesMap.has_key(child):
167 # create the new node and store it
168 newNode = Node(child)
169 newNode.depth = depth
170 else:
171 newNode = nodesMap[child]
172 # If the current depth doesn't exceed the maximum depth, add newNode to recursion step
173 if depth <= self.maxdepth:
174 # The node is appended to the nodes list for the next recursion step.
175 nodesMap[child] = newNode
176 self.all_nodes.append(newNode)
177 child_nodes.append(newNode)
178 node.append(newNode)
179 # Draw an edge.
180 edge = (node, newNode)
181 if not edge in self.all_edges:
182 self.all_edges.append(edge)
183 # recurse, if the current recursion step yields children
184 if len(child_nodes):
185 self.recurse_build(child_nodes, depth+1, nodesMap)
186
187 class Node:
188 def __init__(self, name):
189 self.name = name
190 self.children = []
191 self.visited = 0
192 self.linkedfrom = 0
193 self.linkedto = 0
194 self.depth = 0
195
196 def append(self, node):
197 self.children.append(node)
198
199 def execute(pagename, request):
200 _ = request.getText
201
202 maxdepth = DEFAULT_DEPTH
203 if DEPTH_CONTROL and request.form.has_key('depth'):
204 maxdepth = int(request.form['depth'][0])
205
206 if maxdepth > MAX_DEPTH:
207 maxdepth = MAX_DEPTH
208
209 request.http_headers()
210 wikiutil.send_title(request, _('Visual Map of %s') % pagename, pagename=pagename)
211
212 baseurl = request.getBaseURL()
213
214 wikinamefs = wikiutil.quoteWikinameFS(pagename)
215 wikinameurl = wikiutil.quoteWikinameURL(pagename)
216 fnprefix = os.path.join(CACHE_DIR, '%s_%s' % (wikinamefs, maxdepth))
217 dotfilename = '%s.%s' % (fnprefix, 'dot')
218 imagefilename = '%s.%s' % (fnprefix, OUTPUT_FORMAT)
219 mapfilename = '%s.%s' % (fnprefix, 'cmap')
220 imageurl = '%s/%s_%s.%s' % (CACHE_URL, wikinameurl, maxdepth, OUTPUT_FORMAT)
221
222 lsm = LocalSiteMap(pagename, maxdepth).output(request).encode(config.charset)
223
224 dotfile = file(dotfilename, 'w')
225 dotfile.write('digraph G {\n')
226 if OUTPUT_SIZE:
227 dotfile.write(' size="%s"\n' % OUTPUT_SIZE)
228 dotfile.write(' ratio=compress;\n')
229 dotfile.write(' URL="%s";\n' % wikinameurl)
230 dotfile.write(' overlap=false;\n')
231 dotfile.write(' concentrate=true;\n')
232 dotfile.write(' edge [color="%s"];\n' % EDGE_COLOR)
233 dotfile.write(' node [URL="%s/\N", ' % baseurl)
234 dotfile.write('fontcolor=black, fontname="%s", fontsize=%s, style=filled, color="%s"]\n' % (FONTNAME, FONTSIZE, BOX_COLOR))
235 dotfile.write(lsm)
236 dotfile.write('}\n')
237 dotfile.close()
238
239 os.system('%s -T%s -o"%s" "%s"' % (DOT_EXE, OUTPUT_FORMAT, imagefilename, dotfilename))
240 os.system('%s -Tcmap -o"%s" "%s"' % (DOT_EXE, mapfilename, dotfilename))
241
242 request.write('<center><img class="sitemap" border="1" src="%s" usemap="#map1"></center>' % (imageurl,))
243 request.write('<map name="map1">')
244 mapfile = file(mapfilename, 'r')
245 for row in mapfile:
246 request.write(row)
247 mapfile.close()
248 request.write('</map>')
249
250 if DEPTH_CONTROL:
251 linkname = wikiutil.quoteWikinameURL(pagename)
252 request.write('<p align="center">')
253 if maxdepth > 1:
254 request.write('<a href="%s/%s?action=VisualSiteMap&depth=%s">Less</a>' % (baseurl, linkname, maxdepth-1))
255 else:
256 request.write('Less')
257 request.write(' | ')
258
259 if maxdepth < MAX_DEPTH:
260 request.write('<a href="%s/%s?action=VisualSiteMap&depth=%s">More</a>' % (baseurl, linkname, maxdepth+1))
261 else:
262 request.write('More')
263 request.write('</p>')
264
265 request.write('<p align="center"><small>Search depth is %s. Nodes linked more than %s times are highlighted.</small></p>' % (maxdepth, STRONG_LINK_NR))
266
267 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.