/// <summary>
        /// Gets rows as enumeration of dictionaries.
        /// </summary>
        /// <param name="rows">Source rows.</param>
        /// <param name="parserProvider"></param>
        /// <returns></returns>
        public static IEnumerable <IReadOnlyDictionary <string, string> > AsDictionaryList(
            this IEnumerable <ExcelElement <Row> > rows,
            IParserProvider parserProvider)
        {
            rows.AssertArgumentNotNull(nameof(rows));
            parserProvider.AssertArgumentNotNull(nameof(parserProvider));

            ExcelElement <HeaderCell>[]? headers = null;
            foreach (var row in rows)
            {
                if (headers == null)
                {
                    // Use first row as headers
                    headers = row.GetHeaders();
                    continue;
                }

                //TODO: Use cached parserProvider
                var rowValues = row.GetRowValues(headers: headers, parserProvider: parserProvider);

                // skip empty line
                if (rowValues.All(string.IsNullOrWhiteSpace))
                {
                    continue;
                }

                var headerNames   = headers.Select(header => header.Data.Name ?? header.Data.ColumnReference).ToArrayDebug();
                var expandoObject = headerNames
                                    .Zip(rowValues, (header, value) => (header, value))
                                    .ToDictionary(tuple => tuple.header, tuple => tuple.value);

                yield return(expandoObject);
            }
        }
        /// <summary>
        /// Parses dictionary to <see cref="IPropertyValue"/> list.
        /// </summary>
        /// <param name="parserProvider"><see cref="IParserProvider"/>.</param>
        /// <param name="sourceRow">Source data.</param>
        /// <returns><see cref="IPropertyValue"/> list.</returns>
        public static IReadOnlyList <IPropertyValue> ParseProperties(this IParserProvider parserProvider, IReadOnlyDictionary <string, string> sourceRow)
        {
            parserProvider.AssertArgumentNotNull(nameof(parserProvider));
            sourceRow.AssertArgumentNotNull(nameof(sourceRow));

            var propertyValues = parserProvider
                                 .GetParsers()
                                 .Select(parser => parser.ParseRowUntyped(sourceRow))
                                 .Where(result => result.IsSuccess)
                                 .Select(result => result.Value)
                                 .ToList();

            return(propertyValues);
        }
        public static IEnumerable <T> MapRows <T>(
            this IEnumerable <ExcelElement <Row> > rows,
            IParserProvider parserProvider,
            Func <IReadOnlyList <IPropertyValue>, T>?factory = null)
        {
            rows.AssertArgumentNotNull(nameof(rows));
            parserProvider.AssertArgumentNotNull(nameof(parserProvider));

            if (factory == null)
            {
                factory = list => (T)Activator.CreateInstance(typeof(T), list);
            }

            return(rows
                   .AsDictionaryList(parserProvider)
                   .Select(parserProvider.ParseProperties)
                   .Select(factory));
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="CachedParserProvider"/> class.
        /// </summary>
        /// <param name="parserProvider">Parser provider to cache.</param>
        public CachedParserProvider(IParserProvider parserProvider)
        {
            parserProvider.AssertArgumentNotNull(nameof(parserProvider));

            _parsers = parserProvider.GetParsers().ToArray();
        }