// // index.cs: Handling of the index files // // Author: // Miguel de Icaza (miguel@xamarin.com) // // (C) 2003 Ximian, Inc. // Copyright 2003-2011 Novell Inc // Copyright 2011 Xamarin Inc. // // Possible file format optimizations: // * Do not use 4 bytes for each index entry, use 3 bytes // * Find a way of compressing strings, there are plenty of duplicates // Find common roots, and use an encoding that uses a root to compress data. // "System", "System.Data", "System.Data class" // 0: PLAIN: "System" // 1: PLAIN: " class" // 2: LINK0 PLAIN ".DATA" // 3: LINK0 LINK1 // // Maybe split everything at spaces and dots, and encode that: // string-1-idx "System." // string-1-idx "Data" // 2-items [ string-1-idx string-2-idx] // // Other variations are possible; Like Archive "System", "System." when we // see "System.Data". // // using System; using System.IO; using System.Text; using System.Collections.Generic; namespace Monodoc { public class Topic { public readonly string Caption; public readonly string SortKey; public readonly string Url; public Topic (string caption, string sort_key, string url) { Caption = caption; SortKey = sort_key; Url = url; } } public class IndexEntry { List topics; public int Position { get; private set; } public IList Topics { get { return topics.AsReadOnly (); } } public int Count { get; private set; } public void Add (Topic t) { Count++; topics.Add (t); } public Topic this [int idx] { get { if (idx < 0 || idx > topics.Count) throw new ArgumentOutOfRangeException ("idx"); return topics[idx]; } } // // Constructor from a stream // public IndexEntry (FileStream fs, BinaryReader reader, int position) { Count = reader.ReadInt32 (); int caption_offset = reader.ReadInt32 (); string caption; topics = new List (Count); int [] offsets = new int [Count]; for (int i = 0; i < Count; i++) offsets [i] = reader.ReadInt32 (); fs.Position = caption_offset; caption = reader.ReadString (); for (int i = 0; i < Count; i++){ fs.Position = offsets [i]; string url = reader.ReadString (); topics.Add (new Topic (caption, string.Empty, url)); } } // // Regular constructor public IndexEntry () { topics = new List (); } public void WriteTopics (IndexMaker maker, Stream stream, BinaryWriter writer) { // // Convention: entries with the same SortKey should have the same Caption // Position = (int) stream.Position; writer.Write (Count); if (Count == 0) return; writer.Write (maker.GetCode (topics[0].Caption)); foreach (Topic t in topics) writer.Write (maker.GetCode (t.Url)); } } public class IndexMaker { Dictionary entries = new Dictionary (); Dictionary all_strings = new Dictionary (); int index_position; void AddString (string str) { if (!all_strings.ContainsKey (str)) all_strings.Add (str, 0); } public void AddTopic (Topic topic) { IndexEntry entry; if (!entries.TryGetValue (topic.SortKey, out entry)) { entry = new IndexEntry (); entries[topic.SortKey] = entry; } AddString (topic.SortKey); AddString (topic.Caption); AddString (topic.Url); entry.Add (topic); } public void Add (string caption, string sort_key, string url) { Topic t = new Topic (caption, sort_key, url); AddTopic (t); } void SaveStringTable (Stream stream, BinaryWriter writer) { var keys = new List (all_strings.Keys); foreach (string s in keys) { int pos = (int) stream.Position; writer.Write (s); all_strings [s] = pos; } } public int GetCode (string s) { return all_strings [s]; } void SaveTopics (Stream stream, BinaryWriter writer) { // // Convention: entries with the same SortKey should have the same Caption // foreach (IndexEntry e in entries.Values) e.WriteTopics (this, stream, writer); } void SaveIndexEntries (Stream stream, BinaryWriter writer) { index_position = (int) stream.Position; writer.Write (entries.Count); var keys = new List (entries.Keys); keys.Sort (StringComparer.OrdinalIgnoreCase); foreach (string s in keys){ IndexEntry e = entries [s]; writer.Write (e.Position); } } public void Save (string filename) { Encoding utf8 = new UTF8Encoding (false, true); using (FileStream fs = File.OpenWrite (filename)){ BinaryWriter writer = new BinaryWriter (fs, utf8); writer.Write (new byte [] { (byte) 'M', (byte) 'o', (byte) 'i', (byte) 'x'}); // Leave room for pointer fs.Position = 8; SaveStringTable (fs, writer); SaveTopics (fs, writer); // index_position is set here SaveIndexEntries (fs, writer); fs.Position = 4; writer.Write (index_position); } } } public interface IListModel { int Rows { get; } string GetValue (int row); string GetDescription (int row); } public class IndexReader : IListModel { Encoding utf8 = new UTF8Encoding (false, true); FileStream fs; BinaryReader reader; // The offset of the table of entries int table_offset; int entries; static public IndexReader Load (string filename) { if (!File.Exists (filename)) return null; try { return new IndexReader (filename); } catch { return null; } } IndexReader (string filename) { fs = File.OpenRead (filename); reader = new BinaryReader (fs, utf8); if (fs.ReadByte () != 'M' || fs.ReadByte () != 'o' || fs.ReadByte () != 'i' || fs.ReadByte () != 'x'){ throw new Exception ("Corrupt index"); } // Seek to index_entries fs.Position = reader.ReadInt32 (); entries = reader.ReadInt32 (); table_offset = (int) fs.Position; } public int Rows { get { return entries; } } public string GetValue (int row) { fs.Position = row * 4 + table_offset; fs.Position = reader.ReadInt32 () + 4; int code = reader.ReadInt32 (); fs.Position = code; string caption = reader.ReadString (); return caption; } public string GetDescription (int row) { return GetValue (row); } public IndexEntry GetIndexEntry (int row) { fs.Position = row * 4 + table_offset; int entry_offset = reader.ReadInt32 (); fs.Position = entry_offset; return new IndexEntry (fs, reader, entry_offset); } } }