/// <summary> /// Adds a new RantDictionaryTable object to the collection. /// </summary> /// <param name="table"></param> /// <param name="mergeBehavior">The merging strategy to employ.</param> public void AddTable(RantDictionaryTable table, TableMergeBehavior mergeBehavior = TableMergeBehavior.Naive) { RantDictionaryTable oldTable; if (_tables.TryGetValue(table.Name, out oldTable)) { oldTable.Merge(table, mergeBehavior); } else { _tables[table.Name] = table; } }
/// <summary> /// Adds a new <see cref="RantDictionaryTable" /> object to the dictionary. /// </summary> /// <param name="table">The table to add.</param> public void AddTable(RantDictionaryTable table) { if (_tables.TryGetValue(table.Name, out RantDictionaryTable oldTable)) { if (ReferenceEquals(table, oldTable)) { return; } oldTable.Merge(table); } else { _tables[table.Name] = table; } }
/// <summary> /// Adds another table's entries to the current table, given that they share the same name and term count. /// </summary> /// <param name="other">The table whose entries will be added to the current instance.</param> /// <returns>True if merge succeeded; otherwise, False.</returns> public bool Merge(RantDictionaryTable other) { if (other.Name != Name || other == this) { return(false); } if (other.TermsPerEntry != TermsPerEntry) { return(false); } _entriesHash.AddRange(other._entriesHash); _entriesList.AddRange(other._entriesHash); _dirty = true; RebuildCache(); return(true); }
/// <summary> /// Adds another table's entries to the current table, given that they share the same name and subtypes. /// </summary> /// <param name="other">The table whose entries will be added to the current instance.</param> /// <param name="mergeBehavior">The merging strategy to employ.</param> /// <returns>True if merge succeeded; otherwise, False.</returns> public bool Merge(RantDictionaryTable other, TableMergeBehavior mergeBehavior = TableMergeBehavior.Naive) { if (other._name != _name || other == this) { return(false); } if (!other._subtypes.SequenceEqual(_subtypes)) { return(false); } int oldLength = _entries.Length; switch (mergeBehavior) { case TableMergeBehavior.Naive: Array.Resize(ref _entries, _entries.Length + other._entries.Length); Array.Copy(other._entries, 0, _entries, oldLength, other._entries.Length); break; case TableMergeBehavior.RemoveEntryDuplicates: // TODO: Make this NOT O(n^2*subtypes) -- speed up with HashSet? { var otherEntries = other._entries.Where(e => !_entries.Any(ee => ee.Terms.SequenceEqual(e.Terms))).ToArray(); Array.Resize(ref _entries, _entries.Length + otherEntries.Length); Array.Copy(otherEntries, 0, _entries, oldLength, otherEntries.Length); break; } case TableMergeBehavior.RemoveFirstTermDuplicates: // TODO: Make this NOT O(n^2) { var otherEntries = other._entries.Where(e => _entries.All(ee => ee.Terms[0] != e.Terms[0])).ToArray(); Array.Resize(ref _entries, _entries.Length + otherEntries.Length); Array.Copy(otherEntries, 0, _entries, oldLength, otherEntries.Length); break; } } return(true); }
public void BuildCache(RantDictionaryTable table) { lock (syncObject) { ClearAll(); foreach (var entry in table.GetEntries()) { foreach (var cl in entry.GetClasses()) { HashSet <RantDictionaryEntry> set; _allClasses.Add(cl); if (!_cache.TryGetValue(cl, out set)) { set = _cache[cl] = GetPool(); } set.Add(entry); } } foreach (var cl in _allClasses) { if (!_invCache.TryGetValue(cl, out HashSet <RantDictionaryEntry> set)) { set = _invCache[cl] = GetPool(); } foreach (var entry in table.GetEntries()) { if (!entry.ContainsClass(cl)) { set.Add(entry); } } } } }
public static void ConstructTable(string origin, string name, Dictionary <string, int> subs, ref int termCount, ref RantDictionaryTable table) { if (table != null) { return; } if (name == null) { throw new RantTableLoadException(origin, 1, 1, "err-table-missing-name"); } if (termCount == 0 || subs.Count == 0) { subs.Add("default", 0); termCount = 1; } table = new RantDictionaryTable(name, termCount); foreach (var sub in subs) { table.AddSubtype(sub.Key, sub.Value); } }
/// <summary> /// Loads all dictionary (.dic) files from the specified directory and returns a Dictionary object that contains the loaded data. /// </summary> /// <param name="directory">The directory from which to load dictionaries.</param> /// <param name="filter">Indicates whether dictionary entries marked with the #nsfw flag should be loaded.</param> /// <returns></returns> public static RantDictionary FromDirectory(string directory, NsfwFilter filter) { return(new RantDictionary(Directory.GetFiles(directory, "*.dic", SearchOption.AllDirectories).Select(file => RantDictionaryTable.FromFile(file, filter)).ToList())); }
/// <summary> /// Adds a new RantDictionaryTable object to the collection. /// </summary> /// <param name="dictionary"></param> public void AddTable(RantDictionaryTable dictionary) { _tables[dictionary.Name] = dictionary; }
public IEnumerable <RantDictionaryEntry> Filter(IEnumerable <ClassFilterRule> rules, RantDictionary dictionary, RantDictionaryTable table) { lock (syncObject) { int startIndex = 0; var ruleArray = rules as ClassFilterRule[] ?? rules.ToArray(); HashSet <RantDictionaryEntry> setCached; var set = GetPool(); var hide = table.HiddenClasses // Exclude overridden hidden classes .Except(dictionary.IncludedHiddenClasses) // Exclude hidden classes filtered for .Where(cl => !ruleArray.Any(rule => rule.ShouldMatch && String.Equals(rule.Class, cl, StringComparison.InvariantCultureIgnoreCase))).ToArray(); // Get initial pool if (hide.Length > 0) { // Retrieve the inverse pool of the first hidden class if (!_invCache.TryGetValue(hide[0], out setCached)) { setCached = table.EntriesHash; } } else if (ruleArray.Length > 0) { if (ruleArray[0].ShouldMatch) { if (!_cache.TryGetValue(ruleArray[0].Class, out setCached)) { return(null); } } else { if (!_invCache.TryGetValue(ruleArray[0].Class, out setCached)) { setCached = table.EntriesHash; } } startIndex = 1; } else { setCached = table.EntriesHash; } // Transfer items from cached pool to our new pool and exclude hidden classes foreach (var item in setCached) { if (hide.Length == 0 || !hide.Any(cl => item.ContainsClass(cl))) { set.Add(item); } } // Apply filters by intersecting pools for (int i = startIndex; i < ruleArray.Length; i++) { if (ruleArray[i].ShouldMatch) { if (_cache.TryGetValue(ruleArray[i].Class, out setCached)) { set.IntersectWith(setCached); } else { return(null); } } else { if (_invCache.TryGetValue(ruleArray[i].Class, out setCached)) { set.IntersectWith(setCached); } } } return(set); } }
public static void ReadTerms(string origin, string str, int len, int line, ref int i, RantDictionaryTable table, RantDictionaryEntry activeTemplate, Dictionary <string, RantDictionaryEntry> templates, out RantDictionaryEntry result) { SkipSpace(str, len, ref i); int t = 0; var terms = new RantDictionaryTerm[table.TermsPerEntry]; int split = -1; char c = '\0'; var buffer = new StringBuilder(); var white = new StringBuilder(); while (i < len) { switch (c = str[i++]) { // Inline comment case '#': goto done; // Phrasal split operator case '+': if (split > -1) { throw new RantTableLoadException(origin, line, i, "err-table-multiple-splits"); } white.Length = 0; split = buffer.Length; SkipSpace(str, len, ref i); break; // Term reference case '[': { SkipSpace(str, len, ref i); if (i >= len) { throw new RantTableLoadException(origin, line, i, "err-table-incomplete-term-reference"); } int start = i; if (white.Length > 0) { buffer.Append(white); white.Length = 0; } switch (str[i++]) { // Current term from active template case ']': if (t == -1) { throw new RantTableLoadException(origin, line, start + 1, "err-table-no-template"); } buffer.Append(activeTemplate[t].Value); break; // Custom term from active template case '.': { if (activeTemplate == null) { throw new RantTableLoadException(origin, line, start + 1, "err-table-no-template"); } while (i < len && IsValidSubtypeChar(str[i])) { i++; // Read subtype name } if (str[i] != ']') { throw new RantTableLoadException(origin, line, i, "err-table-incomplete-term-reference"); } string subName = str.Substring(start + 1, i - start - 1); if (subName.Length == 0) { throw new RantTableLoadException(origin, line, start + 1, "err-table-empty-subtype-reference"); } int templateSubIndex = table.GetSubtypeIndex(subName); if (templateSubIndex == -1) { throw new RantTableLoadException(origin, line, start + 1, "err-table-nonexistent-subtype", subName); } // Add term value to buffer buffer.Append(activeTemplate[templateSubIndex].Value); i++; // Skip past closing bracket break; } // It is probably a reference to another entry, let's see. default: { while (i < len && IsValidSubtypeChar(str[i]) || str[i] == '.') { i++; } if (str[i] != ']') { throw new RantTableLoadException(origin, line, i, "err-table-incomplete-term-reference"); } var id = str.Substring(start, i - start).Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); switch (id.Length) { // It's just a template ID. case 1: { if (!templates.TryGetValue(id[0], out RantDictionaryEntry entry)) { throw new RantTableLoadException(origin, line, start + 1, "err-table-entry-not-found"); } // Append term value to buffer buffer.Append(entry[t].Value); break; } // Template ID and custom subtype case 2: { if (!templates.TryGetValue(id[0], out RantDictionaryEntry entry)) { throw new RantTableLoadException(origin, line, start + 1, "err-table-entry-not-found"); } int templateSubIndex = table.GetSubtypeIndex(id[1]); if (templateSubIndex == -1 || templateSubIndex >= table.TermsPerEntry) { throw new RantTableLoadException(origin, line, start + 1, "err-table-nonexistent-subtype", id[1]); } buffer.Append(entry[templateSubIndex].Value); break; } // ??? default: throw new RantTableLoadException(origin, line, start + 1, "err-table-invalid-term-reference"); } i++; // Skip past closing bracket break; } } break; } case '\\': { if (white.Length > 0) { buffer.Append(white); white.Length = 0; } switch (c = str[i++]) { case 'n': buffer.Append('\n'); continue; case 'r': buffer.Append('\r'); continue; case 't': buffer.Append('\t'); continue; case 'v': buffer.Append('\v'); continue; case 'f': buffer.Append('\f'); continue; case 'b': buffer.Append('\b'); continue; case 's': buffer.Append(' '); continue; case 'u': { if (i + 4 > len) { throw new RantTableLoadException(origin, line, i + 1, "err-table-incomplete-escape"); } if (!ushort.TryParse(str.Substring(i, 4), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out ushort codePoint)) { throw new RantTableLoadException(origin, line, i + 1, "err-table-unrecognized-codepoint"); } buffer.Append((char)codePoint); i += 4; continue; } case 'U': { if (i + 8 > len) { throw new RantTableLoadException(origin, line, i + 1, "err-table-incomplete-escape"); } if (!Util.TryParseSurrogatePair(str.Substring(i, 8), out char high, out char low)) { throw new RantTableLoadException(origin, line, i + 1, "err-table-unrecognized-codepoint"); } buffer.Append(high).Append(low); i += 8; continue; } default: buffer.Append(c); continue; } continue; } case ',': if (t >= terms.Length) { throw new RantTableLoadException(origin, line, i, "err-table-too-many-terms", terms.Length, t); } terms[t++] = new RantDictionaryTerm(buffer.ToString(), split); buffer.Length = 0; white.Length = 0; split = -1; SkipSpace(str, len, ref i); break; default: if (char.IsWhiteSpace(c)) { white.Append(c); } else { if (white.Length > 0) { buffer.Append(white); white.Length = 0; } buffer.Append(c); } continue; } } done: if (t != terms.Length - 1) { throw new RantTableLoadException(origin, line, i, "err-table-too-few-terms", terms.Length, t + 1); } terms[t] = new RantDictionaryTerm(buffer.ToString()); result = new RantDictionaryEntry(terms); // Add classes from template if (activeTemplate != null) { foreach (string cl in activeTemplate.GetRequiredClasses()) { result.AddClass(cl, false); } foreach (string cl in activeTemplate.GetOptionalClasses()) { result.AddClass(cl, true); } } }
static void GenerateTable(string tablePath, RantDictionaryTable table, string rootDir, string entriesDir) { var tableClasses = new HashSet<string>(); foreach (var entry in table.GetEntries()) { foreach (var entryClass in entry.GetClasses()) { tableClasses.Add(entryClass); } } File.WriteAllText(rootDir + "/" + "table-" + table.Name + ".html", PageGenerator.GenerateTablePage(table, tablePath)); foreach (var tableClass in tableClasses) { File.WriteAllText(entriesDir + "/" + table.Name + "-" + tableClass + ".html", PageGenerator.GenerateTableClassPage(table, tableClass)); } File.WriteAllText(entriesDir + "/" + table.Name + ".html", PageGenerator.GenerateTableClassPage(table, "")); }
public static string GenerateTablePage(RantDictionaryTable table, string filename) { int entryCount = table.GetEntries().Count(); // Get all the classes var tableClasses = new HashSet<string>(); foreach (var entryClass in table.GetEntries().SelectMany(entry => entry.GetClasses())) { tableClasses.Add(entryClass); } var text = new StringWriter(); using (var writer = new HtmlTextWriter(text)) { writer.WriteLine("<!DOCTYPE html>"); writer.RenderBeginTag(HtmlTextWriterTag.Html); // Header writer.RenderBeginTag(HtmlTextWriterTag.Head); // Title writer.RenderBeginTag(HtmlTextWriterTag.Title); writer.WriteEncodedText(table.Name); writer.RenderEndTag(); // Stylesheet writer.AddAttribute(HtmlTextWriterAttribute.Rel, "stylesheet"); writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/css"); writer.AddAttribute(HtmlTextWriterAttribute.Href, "dicdoc.css"); writer.RenderBeginTag(HtmlTextWriterTag.Link); writer.RenderEndTag(); writer.RenderEndTag(); // </head> writer.RenderBeginTag(HtmlTextWriterTag.Body); // Header writer.RenderBeginTag(HtmlTextWriterTag.H1); writer.WriteEncodedText("<" + table.Name + ">"); writer.RenderEndTag(); // Description writer.RenderBeginTag(HtmlTextWriterTag.P); writer.WriteEncodedText("The "); writer.RenderBeginTag(HtmlTextWriterTag.B); writer.WriteEncodedText(table.Name); writer.RenderEndTag(); // </b> writer.WriteEncodedText( $" table ({Path.GetFileName(filename)}) contains {entryCount} {(entryCount == 1 ? " entry" : " entries")} and {tableClasses.Count} {(tableClasses.Count == 1 ? " class" : " classes")}."); writer.RenderEndTag(); // </p> // Subtypes if (table.Subtypes.Length == 1) { writer.WriteEncodedText("It has one subtype: "); writer.RenderBeginTag(HtmlTextWriterTag.B); writer.WriteEncodedText(table.Subtypes[0]); writer.RenderEndTag(); writer.WriteEncodedText("."); } else { writer.WriteEncodedText("It has " + table.Subtypes.Length + (table.Subtypes.Length == 1 ? " subtype" : " subtypes") + ": "); for(int i = 0; i < table.Subtypes.Length; i++) { if (i == table.Subtypes.Length - 1) writer.WriteEncodedText(table.Subtypes.Length == 2 ? " and " : "and "); writer.RenderBeginTag(HtmlTextWriterTag.B); writer.WriteEncodedText(table.Subtypes[i]); writer.RenderEndTag(); if (i < table.Subtypes.Length - 1 && table.Subtypes.Length > 2) writer.WriteEncodedText(", "); } writer.WriteEncodedText("."); } // Separator writer.RenderBeginTag(HtmlTextWriterTag.Hr); writer.RenderEndTag(); // "View All" link writer.AddAttribute(HtmlTextWriterAttribute.Href, "entries/" + table.Name + ".html"); writer.RenderBeginTag(HtmlTextWriterTag.A); writer.WriteEncodedText("Browse All Entries"); writer.RenderEndTag(); // </a> // Class list writer.RenderBeginTag(HtmlTextWriterTag.H2); writer.WriteEncodedText("Classes"); writer.RenderEndTag(); // </h2> writer.RenderBeginTag(HtmlTextWriterTag.Ul); foreach (var tableClass in tableClasses) { writer.RenderBeginTag(HtmlTextWriterTag.Li); writer.AddAttribute(HtmlTextWriterAttribute.Href, "entries/" + table.Name + "-" + tableClass + ".html"); writer.RenderBeginTag(HtmlTextWriterTag.A); writer.WriteEncodedText(tableClass); writer.RenderEndTag(); // </a> writer.RenderEndTag(); // </li> } writer.RenderEndTag(); // </ul> writer.RenderEndTag(); // </body> writer.RenderEndTag(); // </html> } return text.ToString(); }
/// <summary> /// Adds another table's entries to the current table, given that they share the same name and subtypes. /// </summary> /// <param name="other">The table whose entries will be added to the current instance.</param> /// <param name="mergeBehavior">The merging strategy to employ.</param> /// <returns>True if merge succeeded; otherwise, False.</returns> public bool Merge(RantDictionaryTable other, TableMergeBehavior mergeBehavior = TableMergeBehavior.Naive) { if (other._name != _name || other == this) return false; if (!other._subtypes.SequenceEqual(_subtypes)) return false; int oldLength = _entries.Length; switch (mergeBehavior) { case TableMergeBehavior.Naive: Array.Resize(ref _entries, _entries.Length + other._entries.Length); Array.Copy(other._entries, 0, _entries, oldLength, other._entries.Length); break; case TableMergeBehavior.RemoveEntryDuplicates: // TODO: Make this NOT O(n^2*subtypes) -- speed up with HashSet? { var otherEntries = other._entries.Where(e => !_entries.Any(ee => ee.Terms.SequenceEqual(e.Terms))).ToArray(); Array.Resize(ref _entries, _entries.Length + otherEntries.Length); Array.Copy(otherEntries, 0, _entries, oldLength, otherEntries.Length); break; } case TableMergeBehavior.RemoveFirstTermDuplicates: // TODO: Make this NOT O(n^2) { var otherEntries = other._entries.Where(e => _entries.All(ee => ee.Terms[0] != e.Terms[0])).ToArray(); Array.Resize(ref _entries, _entries.Length + otherEntries.Length); Array.Copy(otherEntries, 0, _entries, oldLength, otherEntries.Length); break; } } return true; }
/// <summary> /// Loads a package from the specified stream and returns it as a RantPackage object. /// </summary> /// <param name="source">The stream to load the package data from.</param> /// <returns></returns> public static RantPackage Load(Stream source) { using (var reader = new EasyReader(source)) { var magic = Encoding.ASCII.GetString(reader.ReadBytes(4)); if (magic == OLD_MAGIC) return LoadOldPackage(reader); if (magic != MAGIC) throw new InvalidDataException("File is corrupt."); var package = new RantPackage(); var version = reader.ReadUInt32(); if (version != PACKAGE_VERSION) throw new InvalidDataException("Invalid package version: " + version); var compress = reader.ReadBoolean(); var size = reader.ReadInt32(); var data = reader.ReadBytes(size); if (compress) data = EasyCompressor.Decompress(data); var doc = BsonDocument.Read(data); var info = doc["info"]; if (info == null) throw new InvalidDataException("Metadata is missing from package."); package.Title = info["title"]; package.ID = info["id"]; package.Version = RantPackageVersion.Parse(info["version"]); package.Description = info["description"]; package.Authors = (string[])info["authors"]; package.Tags = (string[])info["tags"]; var deps = info["dependencies"]; if (deps != null && deps.IsArray) { for(int i = 0; i < deps.Count; i++) { var dep = deps[i]; var depId = dep["id"].Value; var depVersion = dep["version"].Value; bool depAllowNewer = (bool)dep["allow-newer"].Value; package.AddDependency(new RantPackageDependency(depId.ToString(), depVersion.ToString()) { AllowNewer = depAllowNewer }); } } var patterns = doc["patterns"]; if(patterns != null) { var names = patterns.Keys; foreach (string name in names) package.AddPattern(new RantPattern(name, RantPatternOrigin.File, patterns[name])); } var tables = doc["tables"]; if(tables != null) { var names = tables.Keys; foreach(string name in names) { var table = tables[name]; string tableName = table["name"]; string[] tableSubs = (string[])table["subs"]; string[] hiddenClasses = (string[])table["hidden"]; var entries = new List<RantDictionaryEntry>(); var entryList = table["entries"]; for(var i = 0; i < entryList.Count; i++) { var loadedEntry = entryList[i]; int weight = 1; if (loadedEntry.HasKey("weight")) weight = (int)loadedEntry["weight"].Value; string[] requiredClasses = (string[])loadedEntry["classes"]; string[] optionalClasses = (string[])loadedEntry["optional_classes"]; var terms = new List<RantDictionaryTerm>(); var termList = loadedEntry["terms"]; for(var j = 0; j < termList.Count; j++) { var t = new RantDictionaryTerm(termList[j]["value"], termList[j]["pron"]); terms.Add(t); } var entry = new RantDictionaryEntry( terms.ToArray(), requiredClasses.Concat(optionalClasses.Select(x => x + "?")), weight ); entries.Add(entry); } var rantTable = new RantDictionaryTable( tableName, tableSubs, entries, hiddenClasses ); package.AddTable(rantTable); } } return package; } }
/// <summary> /// Loads all dictionary (.dic) files from the specified directories and returns a Dictionary object that contains the loaded data. /// </summary> /// <param name="directories">The directories from which to load dictionaries.</param> /// <param name="filter">Indicates whether dictionary entries marked with the #nsfw flag should be loaded.</param> /// <returns></returns> public static RantDictionary FromMultiDirectory(string[] directories, NsfwFilter filter) { return(new RantDictionary(directories.SelectMany(path => Directory.GetFiles(path, "*.dic", SearchOption.AllDirectories)).Select(file => RantDictionaryTable.FromFile(file, filter)))); }
public static string GenerateTableClassPage(RantDictionaryTable table, string tableClass) { bool all = String.IsNullOrEmpty(tableClass); var entries = all ? table.GetEntries() : table.GetEntries().Where(e => e.ContainsClass(tableClass)); var text = new StringWriter(); using (var writer = new HtmlTextWriter(text)) { writer.WriteLine("<!DOCTYPE html>"); writer.RenderBeginTag(HtmlTextWriterTag.Html); // Header writer.RenderBeginTag(HtmlTextWriterTag.Head); // Title writer.RenderBeginTag(HtmlTextWriterTag.Title); writer.WriteEncodedText((all ? table.Name : table.Name + ": " + tableClass) + " entries"); writer.RenderEndTag(); // Stylesheet writer.AddAttribute(HtmlTextWriterAttribute.Rel, "stylesheet"); writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/css"); writer.AddAttribute(HtmlTextWriterAttribute.Href, "../dicdoc.css"); writer.RenderBeginTag(HtmlTextWriterTag.Link); writer.RenderEndTag(); writer.RenderEndTag(); // </head> // Body writer.RenderBeginTag(HtmlTextWriterTag.Body); // Heading writer.RenderBeginTag(HtmlTextWriterTag.H1); writer.WriteEncodedText("<" + table.Name + (all ? "" : "-" + tableClass) + ">"); writer.RenderEndTag(); // Entry list foreach (var e in entries) { writer.AddAttribute(HtmlTextWriterAttribute.Class, "entry"); writer.RenderBeginTag(HtmlTextWriterTag.Div); // Terms writer.AddAttribute(HtmlTextWriterAttribute.Class, "termset"); writer.RenderBeginTag(HtmlTextWriterTag.Div); for (int i = 0; i < e.Terms.Length; i++) { writer.AddAttribute(HtmlTextWriterAttribute.Class, "term"); writer.RenderBeginTag(HtmlTextWriterTag.Span); writer.WriteEncodedText(e.Terms[i].Value); if (e.Terms[i].PronunciationParts.Length > 0) { writer.AddAttribute(HtmlTextWriterAttribute.Class, "terminfo"); writer.RenderBeginTag(HtmlTextWriterTag.Span); writer.RenderBeginTag(HtmlTextWriterTag.I); writer.WriteEncodedText(" [" + e.Terms[i].Pronunciation + "]"); writer.RenderEndTag(); // </i> writer.RenderEndTag(); // </span> } // Subtype writer.AddAttribute(HtmlTextWriterAttribute.Class, "subtype"); writer.RenderBeginTag(HtmlTextWriterTag.Span); writer.WriteEncodedText(i < table.Subtypes.Length ? table.Subtypes[i] : "???"); writer.RenderEndTag(); writer.RenderEndTag(); } writer.RenderEndTag(); // Notes var notes = new List<HtmlGenFunc>(); var otherClasses = e.GetClasses().Where(cl => cl != tableClass); if (e.Terms.All(t => t.PronunciationParts.Length > 0)) notes.Add(html => html.WriteEncodedText("Full pronunciation")); else if (e.Terms.Any(t => t.PronunciationParts.Length > 0)) notes.Add(html => html.WriteEncodedText("Partial pronunciation")); if (e.Weight != 1) notes.Add(html => html.WriteEncodedText("Weight: " + e.Weight)); if (all && e.GetClasses().Any()) { notes.Add(html => { html.WriteEncodedText("Classes: "); var classes = e.GetClasses().ToArray(); for (int i = 0; i < classes.Length; i++) { if (i > 0) html.WriteEncodedText(", "); html.AddAttribute(HtmlTextWriterAttribute.Href, $"{table.Name}-{classes[i]}.html"); html.RenderBeginTag(HtmlTextWriterTag.A); html.WriteEncodedText(classes[i]); html.RenderEndTag(); } }); } else if (otherClasses.Any()) { notes.Add(html => { html.WriteEncodedText("Classes: "); var classes = otherClasses.ToArray(); for (int i = 0; i < classes.Length; i++) { if (i > 0) html.WriteEncodedText(", "); html.AddAttribute(HtmlTextWriterAttribute.Href, $"{table.Name}-{classes[i]}.html"); html.RenderBeginTag(HtmlTextWriterTag.A); html.WriteEncodedText(classes[i]); html.RenderEndTag(); } }); } if (e.ContainsClass("nsfw")) notes.Add(html => html.WriteEncodedText("NSFW")); GenerateUL(writer, notes); writer.RenderEndTag(); } writer.RenderEndTag(); // </body> writer.RenderEndTag(); // </html> } return text.ToString(); }
/// <summary> /// Loads a table from the specified stream. /// </summary> /// <param name="origin">The origin of the stream. This will typically be a file path or package name.</param> /// <param name="stream">The stream to load the table from.</param> /// <returns></returns> public static RantDictionaryTable FromStream(string origin, Stream stream) { string name = null; // Stores the table name before final table construction int termsPerEntry = 0; // Stores the term count var subtypes = new Dictionary <string, int>(); // Stores subtypes before final table construction var hidden = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase); RantDictionaryTable table = null; // The table object, constructed when first entry is found string l; // Current line string int line = 0; // Current line number int len, i; // Length and character index of current line bool dummy = false; // Determines if the next entry is a dummy entry string tId = null; // Template ID RantDictionaryEntry activeTemplate = null; // Current template var templates = new Dictionary <string, RantDictionaryEntry>(); RantDictionaryEntry currentEntry = null; var autoClasses = new HashSet <string>(); var autoClassStack = new Stack <List <string> >(); using (var reader = new StreamReader(stream)) { while (!reader.EndOfStream) { line++; // Skip blank lines if (Util.IsNullOrWhiteSpace(l = reader.ReadLine())) { continue; } // Update line info len = l.Length; i = 0; // Skip whitespace at the start of the line while (i < len && char.IsWhiteSpace(l[i])) { i++; } switch (l[i++]) { // Comments case '#': continue; // Directive case '@': { // Read directive name int dPos = i; if (!Tools.ReadDirectiveName(l, len, ref i, out string directiveName)) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-missing-directive-name"); } // Read arguments var args = new List <Argument>(); while (Tools.ReadArg(origin, l, len, line, ref i, out Argument arg)) { args.Add(arg); } switch (directiveName.ToLowerInvariant()) { // Table name definition case "name": { // Do not allow this to appear anywhere except at the top of the file if (table != null) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-misplaced-header-directive"); } // Do not allow multiple @name directives if (name != null) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-multiple-names"); } // One argument required if (args.Count != 1) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-name-args"); } // Must meet standard identifier requirements if (!Util.ValidateName(args[0].Value)) { throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-invalid-name", args[0].Value); } name = args[0].Value; break; } // Subtype definition case "sub": { // Do not allow this to appear anywhere except at the top of the file if (table != null) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-misplaced-header-directive"); } // @sub requires at least one argument if (args.Count == 0) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-subtype-args"); } // If the first argument is a number, use it as the subtype index. if (Util.ParseInt(args[0].Value, out int termIndex)) { // Disallow negative term indices if (termIndex < 0) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-sub-index-negative", termIndex); } // Requires at least one name if (args.Count < 2) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-sub-missing-name"); } // If the index is outside the current term index range, increase the number. if (termIndex >= termsPerEntry) { termsPerEntry = termIndex + 1; } // Assign all following names to the index for (int j = 1; j < args.Count; j++) { // Validate subtype name if (!Util.ValidateName(args[j].Value)) { throw new RantTableLoadException(origin, line, args[j].CharIndex + 1, "err-table-bad-subtype", args[j].Value); } subtypes[args[j].Value] = termIndex; } } else { // Add to last index termIndex = termsPerEntry++; // Assign all following names to the index foreach (var a in args) { // Validate subtype name if (!Util.ValidateName(a.Value)) { throw new RantTableLoadException(origin, line, a.CharIndex + 1, "err-table-bad-subtype", a.Value); } subtypes[a.Value] = termIndex; } } break; } case "hide": if (args.Count == 0) { break; } foreach (var a in args) { if (!Util.ValidateName(a.Value)) { throw new RantTableLoadException(origin, line, i, "err-table-invalid-class", a.Value); } hidden.Add(String.Intern(a.Value)); } break; case "dummy": if (args.Count != 0) { throw new RantTableLoadException(origin, line, i, "err-table-argc-mismatch", directiveName, 0, args.Count); } dummy = true; break; case "id": if (args.Count != 1) { throw new RantTableLoadException(origin, line, i, "err-table-argc-mismatch", directiveName, 1, args.Count); } if (!Util.ValidateName(args[0].Value)) { throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-bad-template-id", args[0].Value); } tId = args[0].Value; break; case "using": if (args.Count != 1) { throw new RantTableLoadException(origin, line, i, "err-table-argc-mismatch", directiveName, 1, args.Count); } if (!Util.ValidateName(args[0].Value)) { throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-bad-template-id", args[0].Value); } if (!templates.TryGetValue(args[0].Value, out activeTemplate)) { throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-template-not-found", args[0].Value); } break; case "class": { var cList = new List <string>(); if (args.Count == 0) { throw new RantTableLoadException(origin, line, i, "err-table-args-expected", directiveName); } foreach (var cArg in args) { if (!Tools.ValidateClassName(cArg.Value)) { throw new RantTableLoadException(origin, line, cArg.CharIndex + 1, "err-table-invalid-class", cArg.Value); } cList.Add(cArg.Value); autoClasses.Add(cArg.Value); } autoClassStack.Push(cList); break; } case "endclass": { if (args.Count == 0) { if (autoClassStack.Count > 0) { foreach (string cName in autoClassStack.Pop()) { autoClasses.Remove(cName); } } } break; } } break; } // Entry case '>': Tools.ConstructTable(origin, name, subtypes, ref termsPerEntry, ref table); Tools.ReadTerms(origin, l, len, line, ref i, table, activeTemplate, templates, out currentEntry); if (!dummy) { table.AddEntry(currentEntry); } foreach (string autoClass in autoClasses) { currentEntry.AddClass(autoClass); } if (tId != null) { templates[tId] = currentEntry; tId = null; } dummy = false; activeTemplate = null; break; // Property case '-': { Tools.ConstructTable(origin, name, subtypes, ref termsPerEntry, ref table); Tools.SkipSpace(l, len, ref i); // Read property name int dPos = i; if (!Tools.ReadDirectiveName(l, len, ref i, out string propName)) { throw new RantTableLoadException(origin, line, dPos + 1, "err-table-missing-property-name"); } // Read arguments var args = new List <Argument>(); while (Tools.ReadArg(origin, l, len, line, ref i, out Argument arg)) { args.Add(arg); } // No args? Skip it. if (args.Count == 0) { continue; } switch (propName.ToLowerInvariant()) { case "class": foreach (var cArg in args) { if (!Tools.ValidateClassName(cArg.Value)) { throw new RantTableLoadException(origin, line, cArg.CharIndex + 1, "err-table-invalid-class", cArg.Value); } currentEntry.AddClass(cArg.Value); } break; case "weight": { if (!float.TryParse(args[0].Value, out float weight) || weight <= 0) { throw new RantTableLoadException(origin, line, args[0].CharIndex + 1, "err-table-invalid-weight", args[0].Value); } currentEntry.Weight = weight; table.EnableWeighting = true; break; } case "pron": if (args.Count != table.TermsPerEntry) { continue; } for (int j = 0; j < currentEntry.TermCount; j++) { currentEntry[j].Pronunciation = args[j].Value; } break; default: if (args.Count == 1) { currentEntry.SetMetadata(propName, args[0].Value); } else { currentEntry.SetMetadata(propName, args.Select(a => a.Value).ToArray()); } break; } break; } } } } // Add hidden classes foreach (string hc in hidden) { table.HideClass(hc); } table.RebuildCache(); return(table); }
/// <summary> /// Adds the specified table to the package. /// </summary> /// <param name="table">The table to add.</param> public void AddTable(RantDictionaryTable table) => (_tables ?? (_tables = new HashSet<RantDictionaryTable>())).Add(table);
/// <summary> /// Loads all dictionary (.dic) files from the specified directories and returns a Dictionary object that contains the loaded data. /// </summary> /// <param name="directories">The directories from which to load dictionaries.</param> /// <param name="mergeBehavior">The merging strategy to employ.</param> /// <returns></returns> public static RantDictionary FromMultiDirectory(string[] directories, TableMergeBehavior mergeBehavior) { return(new RantDictionary(directories.SelectMany(path => Directory.GetFiles(path, "*.dic", SearchOption.AllDirectories)).Select(file => RantDictionaryTable.FromFile(file)), mergeBehavior)); }