-
Notifications
You must be signed in to change notification settings - Fork 48
/
Copy pathHelpSource.cs
357 lines (303 loc) · 8.34 KB
/
HelpSource.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Diagnostics;
using System.Collections.Generic;
using Mono.Utilities;
using Lucene.Net.Index;
namespace Monodoc
{
public enum SortType {
Caption,
Element
}
//
// The HelpSource class keeps track of the archived data, and its
// tree
//
public
#if LEGACY_MODE
partial
#endif
class HelpSource
{
static int id;
//
// The unique ID for this HelpSource.
//
int source_id;
// The name of the HelpSource, used by all the file (.tree, .zip, ...) used by it
string name;
// The full directory path where the HelpSource files are located
string basePath;
// The tree of this help source
Tree tree;
string treeFilePath;
RootTree rootTree;
IDocCache cache;
IDocStorage storage;
public HelpSource (string base_filename, bool create)
{
this.name = Path.GetFileName (base_filename);
this.basePath = Path.GetDirectoryName (base_filename);
this.treeFilePath = base_filename + ".tree";
this.storage = new Monodoc.Storage.ZipStorage (base_filename + ".zip");
this.cache = DocCacheHelper.GetDefaultCache (Name);
tree = create ? new Tree (this, string.Empty, string.Empty) : new Tree (this, treeFilePath);
source_id = id++;
}
public HelpSource ()
{
tree = new Tree (this, "Blah", "Blah");
source_id = id++;
this.cache = new Caches.NullCache ();
}
public int SourceID {
get {
return source_id;
}
}
public string Name {
get {
return name;
}
}
/* This gives the full path of the source/ directory */
public string BaseFilePath {
get {
return basePath;
}
}
public TraceLevel TraceLevel {
get;
set;
}
public string BaseDir {
get {
return basePath;
}
}
public Tree Tree {
get {
return tree;
}
}
public RootTree RootTree {
get {
return rootTree;
}
set {
rootTree = value;
}
}
public IDocCache Cache {
get {
return cache;
}
}
public IDocStorage Storage {
get {
return storage;
}
protected set {
storage = value;
}
}
// A HelpSource may have a common prefix to its URL, give it here
protected virtual string UriPrefix {
get {
return "dummy:";
}
}
public virtual SortType SortType {
get {
return SortType.Caption;
}
}
/// <summary>
/// Returns a stream from the packaged help source archive
/// </summary>
public virtual Stream GetHelpStream (string id)
{
return storage.Retrieve (id);
}
public virtual Stream GetCachedHelpStream (string id)
{
if (string.IsNullOrEmpty (id))
throw new ArgumentNullException ("id");
if (!cache.CanCache (DocEntity.Text))
return GetHelpStream (id);
if (!cache.IsCached (id))
cache.CacheText (id, GetHelpStream (id));
return cache.GetCachedStream (id);
}
public XmlReader GetHelpXml (string id)
{
var url = "monodoc:///" + SourceID + "@" + Uri.EscapeDataString (id) + "@";
var stream = cache.IsCached (id) ? cache.GetCachedStream (id) : storage.Retrieve (id);
return stream == null ? null : new XmlTextReader (url, stream);
}
public virtual XmlDocument GetHelpXmlWithChanges (string id)
{
XmlDocument doc = new XmlDocument ();
if (!storage.SupportRevision) {
doc.Load (GetHelpXml (id));
} else {
var revManager = storage.RevisionManager;
doc.Load (revManager.RetrieveLatestRevision (id));
}
return doc;
}
public virtual string GetCachedText (string id)
{
if (!cache.CanCache (DocEntity.Text))
return GetText (id);
if (!cache.IsCached (id))
cache.CacheText (id, GetText (id));
return cache.GetCachedString (id);
}
public virtual string GetText (string id)
{
return new StreamReader (GetHelpStream (id)).ReadToEnd ();
}
// Tells if the result for the provided id is generated dynamically
// by the help source
public virtual bool IsGeneratedContent (string id)
{
return false;
}
// Tells if the content of the provided id is meant to be returned raw
public virtual bool IsRawContent (string id)
{
return false;
}
// Tells if provided id refers to a multi-content-type document if it's case
// tells the ids it's formed of
public virtual bool IsMultiPart (string id, out IEnumerable<string> parts)
{
parts = null;
return false;
}
/// <summary>
/// Saves the tree and the archive
/// </summary>
public void Save ()
{
tree.Save (treeFilePath);
storage.Dispose ();
}
public virtual void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
{
throw new NotImplementedException ();
}
public virtual string GetPublicUrl (Node node)
{
return node.GetInternalUrl ();
}
public virtual bool CanHandleUrl (string url)
{
return url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase);
}
public virtual string GetInternalIdForUrl (string url, out Node node, out Dictionary<string, string> context)
{
context = null;
node = MatchNode (url);
return node == null ? null : url.Substring (UriPrefix.Length);
}
public virtual Node MatchNode (string url)
{
Node current = null;
var matchCache = LRUCache<string, Node>.Default;
if ((current = matchCache.Get (url)) != null)
return current;
current = Tree.RootNode;
var strippedUrl = url.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase) ? url.Substring (UriPrefix.Length) : url;
var searchNode = new Node () { Element = strippedUrl };
do {
int index = current.ChildNodes.BinarySearch (searchNode, NodeElementComparer.Instance);
if (index >= 0) {
Node n = current.ChildNodes[index];
matchCache.Put (url, n);
return n;
}
index = ~index;
if (index == current.ChildNodes.Count) {
return SlowMatchNode (Tree.RootNode, matchCache, strippedUrl);
}
if (index == 0)
return null;
current = current.ChildNodes [index - 1];
} while (true);
return null;
}
/* That slow path is mainly here to handle ecmaspec type of url which are composed of hard to sort numbers
* because they don't have the same amount of digit. We could use a regex to harmonise the various number
* parts but then it would be quite specific. Since in the case of ecmaspec the tree is well-formed enough
* the "Slow" match should still be fast enough
*/
Node SlowMatchNode (Node current, LRUCache<string, Node> matchCache, string url)
{
//Console.WriteLine ("Entering slow path for {0} starting from {1}", url, current.Element);
while (current != null) {
bool stop = true;
foreach (Node n in current.ChildNodes) {
var element = n.Element.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase) ? n.Element.Substring (UriPrefix.Length) : n.Element;
if (url.Equals (element, StringComparison.Ordinal)) {
matchCache.Put (url, n);
return n;
} else if (url.StartsWith (element + ".", StringComparison.OrdinalIgnoreCase) && !n.IsLeaf) {
current = n;
stop = false;
break;
}
}
if (stop)
current = null;
}
return null;
}
class NodeElementComparer : IComparer<Node>
{
public static NodeElementComparer Instance = new NodeElementComparer ();
public int Compare (Node n1, Node n2)
{
return string.Compare (Cleanup (n1), Cleanup (n2), StringComparison.Ordinal);
}
string Cleanup (Node n)
{
var prefix = n.Tree != null && n.Tree.HelpSource != null ? n.Tree.HelpSource.UriPrefix : string.Empty;
var element = n.Element.StartsWith (prefix, StringComparison.OrdinalIgnoreCase) ? n.Element.Substring (prefix.Length) : n.Element;
if (char.IsDigit (element, 0)) {
var count = element.TakeWhile (char.IsDigit).Count ();
element = element.PadLeft (Math.Max (0, 3 - count) + element.Length, '0');
}
//Console.WriteLine ("Cleaned up {0} to {1}", n.Element, element);
return element;
}
}
public virtual DocumentType GetDocumentTypeForId (string id)
{
return DocumentType.PlainText;
}
public virtual Stream GetImage (string url)
{
Stream result = null;
storage.TryRetrieve (url, out result);
return result;
}
//
// Populates the index.
//
public virtual void PopulateIndex (IndexMaker index_maker)
{
}
//
// Create different Documents for adding to Lucene search index
// The default action is do nothing. Subclasses should add the docs
//
public virtual void PopulateSearchableIndex (IndexWriter writer)
{
}
}
}