/// <summary>
        /// Loads a RantDictionary from the file at the specified path.
        /// </summary>
        /// <param name="path">The path to the file to load.</param>
        /// <returns></returns>
        public static RantDictionaryTable FromFile(string path)
        {
            var name = "";

            string[] subtypes = { "default" };
            bool     header   = true;

            var scopedClassSet        = new HashSet <string>();
            RantDictionaryEntry entry = null;
            var entries       = new List <RantDictionaryEntry>();
            var entryStringes = new List <Stringe>();
            var types         = new Dictionary <string, EntryTypeDef>();
            var hiddenClasses = new HashSet <string> {
                "nsfw"
            };

            foreach (var token in DicLexer.Tokenize(path, File.ReadAllText(path)))
            {
                switch (token.ID)
                {
                case DicTokenType.Directive:
                {
                    var parts = VocabUtils.GetArgs(token.Value).ToArray();
                    if (!parts.Any())
                    {
                        continue;
                    }
                    var dirName = parts.First().ToLower();
                    var args    = parts.Skip(1).ToArray();

                    switch (dirName)
                    {
                    case "name":
                        if (!header)
                        {
                            LoadError(path, token, "The #name directive may only be used in the file header.");
                        }
                        if (args.Length != 1)
                        {
                            LoadError(path, token, "#name directive expected one word:\r\n\r\n" + token.Value);
                        }
                        if (!Util.ValidateName(args[0]))
                        {
                            LoadError(path, token, $"Invalid #name value: '{args[1]}'");
                        }
                        name = args[0].ToLower();
                        break;

                    case "subs":
                        if (!header)
                        {
                            LoadError(path, token, "The #subs directive may only be used in the file header.");
                        }
                        subtypes = args.Select(s => s.Trim().ToLower()).ToArray();
                        break;

                    case "version":             // Kept here for backwards-compatability
                        if (!header)
                        {
                            LoadError(path, token, "The #version directive may only be used in the file header.");
                        }
                        break;

                    case "hidden":
                        if (!header)
                        {
                            LoadError(path, token, "The #hidden directive may only be used in the file header.");
                        }
                        if (Util.ValidateName(args[0]))
                        {
                            hiddenClasses.Add(args[0]);
                        }
                        break;

                    // Deprecated, remove in Rant 3
                    case "nsfw":
                        scopedClassSet.Add("nsfw");
                        break;

                    // Deprecated, remove in Rant 3
                    case "sfw":
                        scopedClassSet.Remove("nsfw");
                        break;

                    case "class":
                    {
                        if (args.Length < 2)
                        {
                            LoadError(path, token, "The #class directive expects an operation and at least one value.");
                        }
                        switch (args[0].ToLower())
                        {
                        case "add":
                            foreach (var cl in args.Skip(1))
                            {
                                scopedClassSet.Add(cl.ToLower());
                            }
                            break;

                        case "remove":
                            foreach (var cl in args.Skip(1))
                            {
                                scopedClassSet.Remove(cl.ToLower());
                            }
                            break;
                        }
                    }
                    break;

                    case "type":
                    {
                        if (!header)
                        {
                            LoadError(path, token, "The #type directive may only be used in the file header.");
                        }
                        if (args.Length != 3)
                        {
                            LoadError(path, token, "#type directive requires 3 arguments.");
                        }
                        types.Add(args[0], new EntryTypeDef(args[0], args[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries),
                                                            Util.IsNullOrWhiteSpace(args[2]) ? null : new EntryTypeDefFilter(args[2])));
                    }
                    break;
                    }
                }
                break;

                case DicTokenType.Entry:
                {
                    if (Util.IsNullOrWhiteSpace(name))
                    {
                        LoadError(path, token, "Missing table name before entry list.");
                    }
                    if (Util.IsNullOrWhiteSpace(token.Value))
                    {
                        LoadError(path, token, "Encountered empty entry.");
                    }
                    header = false;
                    entry  = new RantDictionaryEntry(token.Value.Split('/').Select(s => s.Trim()).ToArray(), scopedClassSet);
                    entries.Add(entry);
                    entryStringes.Add(token);
                }
                break;

                case DicTokenType.DiffEntry:
                {
                    if (Util.IsNullOrWhiteSpace(name))
                    {
                        LoadError(path, token, "Missing table name before entry list.");
                    }
                    if (Util.IsNullOrWhiteSpace(token.Value))
                    {
                        LoadError(path, token, "Encountered empty entry.");
                    }
                    header = false;
                    string first = null;
                    entry = new RantDictionaryEntry(token.Value.Split('/')
                                                    .Select((s, i) =>
                        {
                            if (i > 0)
                            {
                                return(Diff.Mark(first, s));
                            }
                            return(first = s.Trim());
                        }).ToArray(), scopedClassSet);
                    entries.Add(entry);
                    entryStringes.Add(token);
                }
                break;

                case DicTokenType.Property:
                {
                    var parts = token.Value.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                    if (!parts.Any())
                    {
                        LoadError(path, token, "Empty property field.");
                    }
                    switch (parts[0].ToLower())
                    {
                    case "class":
                    {
                        if (parts.Length < 2)
                        {
                            continue;
                        }
                        foreach (var cl in parts[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            bool opt = cl.EndsWith("?");
                            entry.AddClass(VocabUtils.GetString(opt ? cl.Substring(0, cl.Length - 1) : cl), opt);
                        }
                    }
                    break;

                    case "weight":
                    {
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "'weight' property expected a value.");
                        }
                        int weight;
                        if (!Int32.TryParse(parts[1], out weight))
                        {
                            LoadError(path, token, "Invalid weight value: '" + parts[1] + "'");
                        }
                        entry.Weight = weight;
                    }
                    break;

                    case "pron":
                    {
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "'" + parts[0] + "' property expected a value.");
                        }
                        var pron =
                            parts[1].Split('/')
                            .Select(s => s.Trim())
                            .ToArray();
                        if (subtypes.Length == pron.Length)
                        {
                            for (int i = 0; i < entry.Terms.Length; i++)
                            {
                                entry.Terms[i].Pronunciation = pron[i];
                            }
                        }
                    }
                    break;

                    default:
                    {
                        EntryTypeDef typeDef;
                        if (!types.TryGetValue(parts[0], out typeDef))
                        {
                            LoadError(path, token, $"Unknown property name '{parts[0]}'.");
                        }
                        // Okay, it's a type.
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "Missing type value.");
                        }
                        entry.AddClass(VocabUtils.GetString(parts[1]));
                        if (!typeDef.IsValidValue(parts[1]))
                        {
                            LoadError(path, token, $"'{parts[1]}' is not a valid value for type '{typeDef.Name}'.");
                        }
                        break;
                    }
                    }
                }
                break;
                }
            }

            if (types.Any())
            {
                var eEntries       = entries.GetEnumerator();
                var eEntryStringes = entryStringes.GetEnumerator();
                while (eEntries.MoveNext() && eEntryStringes.MoveNext())
                {
                    foreach (var type in types.Values)
                    {
                        if (!type.Test(eEntries.Current))
                        {
                            // TODO: Find a way to output multiple non-fatal table load errors without making a gigantic exception message.
                            LoadError(path, eEntryStringes.Current, $"Entry '{eEntries.Current}' does not satisfy type '{type.Name}'.");
                        }
                    }
                }
            }

            return(new RantDictionaryTable(name, subtypes, entries, hiddenClasses));
        }
예제 #2
0
            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);
                    }
                }
            }
예제 #3
0
        /// <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);
        }
예제 #4
0
        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);
            }
        }
예제 #5
0
        /// <summary>
        /// Loads a RantDictionary from the file at the specified path.
        /// </summary>
        /// <param name="path">The path to the file to load.</param>
        /// <param name="nsfwFilter">Specifies whether to allow or disallow NSFW entries.</param>
        /// <returns></returns>
        public static RantDictionaryTable FromFile(string path, NsfwFilter nsfwFilter = NsfwFilter.Disallow)
        {
            var name    = "";
            var version = Version;

            string[] subtypes = { "default" };

            bool header = true;

            bool nsfw = false;

            var scopedClassSet = new HashSet <string>();

            RantDictionaryEntry entry = null;

            var entries = new List <RantDictionaryEntry>();

            foreach (var token in DicLexer.Tokenize(File.ReadAllText(path)))
            {
                switch (token.ID)
                {
                case DicTokenType.Directive:
                {
                    var parts = token.Value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    if (!parts.Any())
                    {
                        continue;
                    }

                    switch (parts[0].ToLower())
                    {
                    case "name":
                        if (!header)
                        {
                            LoadError(path, token, "The #name directive may only be used in the file header.");
                        }
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "#name directive expected one word:\r\n\r\n" + token.Value);
                        }
                        if (!Util.ValidateName(parts[1]))
                        {
                            LoadError(path, token, "Invalid #name value: '\{parts[1]}'");
                        }
                        name = parts[1].ToLower();
                        break;

                    case "subs":
                        if (!header)
                        {
                            LoadError(path, token, "The #subs directive may only be used in the file header.");
                        }
                        subtypes = parts.Skip(1).Select(s => s.Trim().ToLower()).ToArray();
                        break;

                    case "version":
                        if (!header)
                        {
                            LoadError(path, token, "The #version directive may only be used in the file header.");
                        }
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "The #version directive requires a value.");
                        }
                        if (!int.TryParse(parts[1], out version))
                        {
                            LoadError(path, token, "Invalid version number '\{parts[1]}'");
                        }
                        if (version > Version)
                        {
                            LoadError(path, token, "Unsupported file version '\{version}'");
                        }
                        break;

                    case "nsfw":
                        nsfw = true;
                        break;

                    case "sfw":
                        nsfw = false;
                        break;

                    case "class":
                    {
                        if (parts.Length < 3)
                        {
                            LoadError(path, token, "The #class directive expects an operation and at least one value.");
                        }
                        switch (parts[1].ToLower())
                        {
                        case "add":
                            foreach (var cl in parts.Skip(2))
                            {
                                scopedClassSet.Add(cl.ToLower());
                            }
                            break;

                        case "remove":
                            foreach (var cl in parts.Skip(2))
                            {
                                scopedClassSet.Remove(cl.ToLower());
                            }
                            break;
                        }
                    }
                    break;
                    }
                }
                break;

                case DicTokenType.Entry:
                {
                    if (nsfwFilter == NsfwFilter.Disallow && nsfw)
                    {
                        continue;
                    }
                    if (Util.IsNullOrWhiteSpace(name))
                    {
                        LoadError(path, token, "Missing dictionary name before entry list.");
                    }
                    if (Util.IsNullOrWhiteSpace(token.Value))
                    {
                        LoadError(path, token, "Encountered empty dictionary entry.");
                    }
                    header = false;
                    entry  = new RantDictionaryEntry(token.Value.Split('/').Select(s => s.Trim()).ToArray(), scopedClassSet, nsfw);
                    entries.Add(entry);
                }
                break;

                case DicTokenType.DiffEntry:
                {
                    if (nsfwFilter == NsfwFilter.Disallow && nsfw)
                    {
                        continue;
                    }
                    if (Util.IsNullOrWhiteSpace(name))
                    {
                        LoadError(path, token, "Missing dictionary name before entry list.");
                    }
                    if (Util.IsNullOrWhiteSpace(token.Value))
                    {
                        LoadError(path, token, "Encountered empty dictionary entry.");
                    }
                    header = false;
                    string first = null;
                    entry = new RantDictionaryEntry(token.Value.Split('/')
                                                    .Select((s, i) =>
                        {
                            if (i > 0)
                            {
                                return(Diff.Mark(first, s));
                            }
                            return(first = s.Trim());
                        }).ToArray(), scopedClassSet, nsfw);
                    entries.Add(entry);
                }
                break;

                case DicTokenType.Property:
                {
                    if (nsfwFilter == NsfwFilter.Disallow && nsfw)
                    {
                        continue;
                    }
                    var parts = token.Value.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                    if (!parts.Any())
                    {
                        LoadError(path, token, "Empty property field.");
                    }
                    switch (parts[0].ToLower())
                    {
                    case "class":
                    {
                        if (parts.Length < 2)
                        {
                            continue;
                        }
                        foreach (var cl in parts[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            bool opt = cl.EndsWith("?");
                            entry.AddClass(VocabUtils.GetString(opt ? cl.Substring(0, cl.Length - 1) : cl), opt);
                        }
                    }
                    break;

                    case "weight":
                    {
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "'weight' property expected a value.");
                        }
                        int weight;
                        if (!Int32.TryParse(parts[1], out weight))
                        {
                            LoadError(path, token, "Invalid weight value: '" + parts[1] + "'");
                        }
                        entry.Weight = weight;
                    }
                    break;

                    case "pron":
                    {
                        if (parts.Length != 2)
                        {
                            LoadError(path, token, "'" + parts[0] + "' property expected a value.");
                        }
                        var pron =
                            parts[1].Split('/')
                            .Select(s => s.Trim())
                            .ToArray();
                        if (subtypes.Length != pron.Length)
                        {
                            LoadError(path, token, "Pronunciation list length must match subtype count.");
                        }

                        for (int i = 0; i < entry.Terms.Length; i++)
                        {
                            entry.Terms[i].Pronunciation = pron[i];
                        }
                    }
                    break;
                    }
                }
                break;
                }
            }
            return(new RantDictionaryTable(name, subtypes, entries));
        }
예제 #6
0
        /// <summary>
        /// Loads a RantDictionary from the file at the specified path.
        /// </summary>
        /// <param name="path">The path to the file to load.</param>
        /// <returns></returns>
        public static RantDictionaryTable FromFile(string path)
        {
            var name = "";
            string[] subtypes = { "default" };
            bool header = true;

            var scopedClassSet = new HashSet<string>();
            RantDictionaryEntry entry = null;
            var entries = new List<RantDictionaryEntry>();
            var entryStringes = new List<Stringe>();
            var types = new Dictionary<string, EntryTypeDef>();
            var hiddenClasses = new HashSet<string> { "nsfw" };

            foreach (var token in DicLexer.Tokenize(path, File.ReadAllText(path)))
            {
                switch (token.ID)
                {
                    case DicTokenType.Directive:
                        {
                            var parts = VocabUtils.GetArgs(token.Value).ToArray();
                            if (!parts.Any()) continue;
                            var dirName = parts.First().ToLower();
                            var args = parts.Skip(1).ToArray();

                            switch (dirName)
                            {
                                case "name":    
                                    if (!header) LoadError(path, token, "The #name directive may only be used in the file header.");
                                    if (args.Length != 1) LoadError(path, token, "#name directive expected one word:\r\n\r\n" + token.Value);
                                    if (!Util.ValidateName(args[0])) LoadError(path, token, $"Invalid #name value: '{args[1]}'");
                                    name = args[0].ToLower();
                                    break;
                                case "subs":
                                    if (!header) LoadError(path, token, "The #subs directive may only be used in the file header.");
                                    subtypes = args.Select(s => s.Trim().ToLower()).ToArray();
                                    break;
                                case "version": // Kept here for backwards-compatability
                                    if (!header) LoadError(path, token, "The #version directive may only be used in the file header.");
                                    break;
                                case "hidden":
                                    if (!header) LoadError(path, token, "The #hidden directive may only be used in the file header.");
                                    if (Util.ValidateName(args[0])) hiddenClasses.Add(args[0]);
                                    break;
                                    // Deprecated, remove in Rant 3
                                case "nsfw":
                                    scopedClassSet.Add("nsfw");
                                    break;
                                    // Deprecated, remove in Rant 3
                                case "sfw":
                                    scopedClassSet.Remove("nsfw");
                                    break;
                                case "class":
                                    {
                                        if (args.Length < 2) LoadError(path, token, "The #class directive expects an operation and at least one value.");
                                        switch (args[0].ToLower())
                                        {
                                            case "add":
                                                foreach (var cl in args.Skip(1))
                                                    scopedClassSet.Add(cl.ToLower());
                                                break;
                                            case "remove":
                                                foreach (var cl in args.Skip(1))
                                                    scopedClassSet.Remove(cl.ToLower());
                                                break;
                                        }
                                    }
                                    break;
                                case "type":
                                    {
                                        if (!header) LoadError(path, token, "The #type directive may only be used in the file header.");
                                        if (args.Length != 3) LoadError(path, token, "#type directive requires 3 arguments.");
                                        types.Add(args[0], new EntryTypeDef(args[0], args[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries),
                                            Util.IsNullOrWhiteSpace(args[2]) ? null : new EntryTypeDefFilter(args[2])));
                                    }
                                    break;
                            }
                        }
                        break;
                    case DicTokenType.Entry:
                        {
                            if (Util.IsNullOrWhiteSpace(name))
                                LoadError(path, token, "Missing table name before entry list.");
                            if (Util.IsNullOrWhiteSpace(token.Value))
                                LoadError(path, token, "Encountered empty entry.");
                            header = false;
                            entry = new RantDictionaryEntry(token.Value.Split('/').Select(s => s.Trim()).ToArray(), scopedClassSet);
                            entries.Add(entry);
                            entryStringes.Add(token);
                        }
                        break;
                    case DicTokenType.DiffEntry:
                        {
                            if (Util.IsNullOrWhiteSpace(name))
                                LoadError(path, token, "Missing table name before entry list.");
                            if (Util.IsNullOrWhiteSpace(token.Value))
                                LoadError(path, token, "Encountered empty entry.");
                            header = false;
                            string first = null;
                            entry = new RantDictionaryEntry(token.Value.Split('/')
                                .Select((s, i) =>
                                {
                                    if (i > 0) return Diff.Mark(first, s);
                                    return first = s.Trim();
                                }).ToArray(), scopedClassSet);
                            entries.Add(entry);
                            entryStringes.Add(token);
                        }
                        break;
                    case DicTokenType.Property:
                        {
                            var parts = token.Value.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                            if (!parts.Any()) LoadError(path, token, "Empty property field.");
                            switch (parts[0].ToLower())
                            {
                                case "class":
                                    {
                                        if (parts.Length < 2) continue;
                                        foreach (var cl in parts[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
                                        {
                                            bool opt = cl.EndsWith("?");
                                            entry.AddClass(VocabUtils.GetString(opt ? cl.Substring(0, cl.Length - 1) : cl), opt);
                                        }
                                    }
                                    break;
                                case "weight":
                                    {
                                        if (parts.Length != 2) LoadError(path, token, "'weight' property expected a value.");
                                        int weight;
                                        if (!Int32.TryParse(parts[1], out weight))
                                            LoadError(path, token, "Invalid weight value: '" + parts[1] + "'");
                                        entry.Weight = weight;
                                    }
                                    break;
                                case "pron":
                                    {
                                        if (parts.Length != 2) LoadError(path, token, "'" + parts[0] + "' property expected a value.");
                                        var pron =
                                            parts[1].Split('/')
                                                .Select(s => s.Trim())
                                                .ToArray();
                                        if (subtypes.Length == pron.Length)
                                        {
                                            for (int i = 0; i < entry.Terms.Length; i++)
                                                entry.Terms[i].Pronunciation = pron[i];
                                        }
                                    }
                                    break;
                                default:
                                    {
                                        EntryTypeDef typeDef;
                                        if (!types.TryGetValue(parts[0], out typeDef))
                                            LoadError(path, token, $"Unknown property name '{parts[0]}'.");
                                        // Okay, it's a type.
                                        if (parts.Length != 2) LoadError(path, token, "Missing type value.");
                                        entry.AddClass(VocabUtils.GetString(parts[1]));
                                        if(!typeDef.IsValidValue(parts[1]))
                                            LoadError(path, token, $"'{parts[1]}' is not a valid value for type '{typeDef.Name}'.");
                                        break;
                                    }
                            }
                        }
                        break;
                }
            }

            if (types.Any())
            {
                var eEntries = entries.GetEnumerator();
                var eEntryStringes = entryStringes.GetEnumerator();
                while (eEntries.MoveNext() && eEntryStringes.MoveNext())
                {
                    foreach (var type in types.Values)
                    {
                        if (!type.Test(eEntries.Current))
                        {
                            // TODO: Find a way to output multiple non-fatal table load errors without making a gigantic exception message.
                            LoadError(path, eEntryStringes.Current, $"Entry '{eEntries.Current}' does not satisfy type '{type.Name}'.");
                        }
                    }
                }
            }

            return new RantDictionaryTable(name, subtypes, entries, hiddenClasses);
        }