/// <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); }
internal override void DeserializeData(EasyReader reader) { this.Name = reader.ReadString(); this.Language = reader.ReadString(); this.TermsPerEntry = reader.ReadInt32(); for (int i = 0; i < TermsPerEntry; i++) { foreach (var subtype in reader.ReadStringArray()) { AddSubtype(subtype, i); } } _hidden.AddRange(reader.ReadStringArray()); int numEntries = reader.ReadInt32(); for (int i = 0; i < numEntries; i++) { var terms = new RantDictionaryTerm[TermsPerEntry]; for (int j = 0; j < TermsPerEntry; j++) { var value = reader.ReadString(); var pron = reader.ReadString(); int valueSplit = reader.ReadInt32(); int pronSplit = reader.ReadInt32(); terms[j] = new RantDictionaryTerm(value, pron, valueSplit, pronSplit); } float weight = reader.ReadSingle(); var entry = new RantDictionaryEntry(terms) { Weight = weight }; foreach (var reqClass in reader.ReadStringArray()) { entry.AddClass(reqClass, false); } foreach (var optClass in reader.ReadStringArray()) { entry.AddClass(optClass, true); } int metaCount = reader.ReadInt32(); for (int j = 0; j < metaCount; j++) { bool isArray = reader.ReadBoolean(); var key = reader.ReadString(); if (isArray) { entry.SetMetadata(key, reader.ReadStringArray()); } else { entry.SetMetadata(key, reader.ReadString()); } } AddEntry(entry); } }