/// <summary> /// Loads the JSON data from a stream. /// </summary> /// <param name="stream">The JSON stream to read.</param> /// <param name="options">The parser options.</param> public static IDictionary <string, string> ParseStream(Stream stream, ParseToDictionaryOptions options = null) { var parserOptions = options ?? new ParseToDictionaryOptions(); var data = new SortedDictionary <string, string>(StringComparer.OrdinalIgnoreCase); var prefixStack = new Stack <string>(); var jsonDocumentOptions = new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; if (parserOptions.Parents != null) { foreach (var parent in parserOptions.Parents) { prefixStack.Push(parent); } } using (var reader = new StreamReader(stream)) { using (var doc = JsonDocument.Parse(reader.ReadToEnd(), jsonDocumentOptions)) { if (doc.RootElement.ValueKind != JsonValueKind.Object) { throw new FormatException($"Top-level JSON element must be an object. Instead, '{doc.RootElement.ValueKind}' was found."); } VisitElement(doc.RootElement, prefixStack, data, parserOptions); } } return(data); }
private static bool HasIdentifier(JsonElement element, Stack <string> prefixStack, ParseToDictionaryOptions parserOptions, out string identifier) { identifier = null; foreach (var property in element.EnumerateObject()) { switch (property.Value.ValueKind) { case JsonValueKind.Number: case JsonValueKind.String: identifier = property.Value.GetString(); if (!string.IsNullOrEmpty(identifier) && parserOptions.IsIdentifier(property.Name, prefixStack)) { return(true); } break; default: continue; } } return(false); }
private static void VisitElement(JsonElement element, Stack <string> prefixStack, IDictionary <string, string> data, ParseToDictionaryOptions parserOptions) { var isEmpty = true; foreach (var property in element.EnumerateObject()) { isEmpty = false; prefixStack.Push(property.Name.Capitalize()); try { VisitValue(property.Value, prefixStack, data, parserOptions); } finally { prefixStack.Pop(); } } if (isEmpty && prefixStack.Count > 0) { var key = string.Join(parserOptions.KeyDelimiter, prefixStack.Reverse()); data[key] = null; } }
/// <summary> /// Creates a new instance of <c>JsonStreamToDictionaryParser</c>. /// </summary> /// <param name="options">The parser options.</param> public JsonStreamToDictionaryParser(ParseToDictionaryOptions options = null) { _options = options; }
private static void VisitValue(JsonElement value, Stack <string> prefixStack, IDictionary <string, string> data, ParseToDictionaryOptions parserOptions) { Debug.Assert(prefixStack.Count > 0); switch (value.ValueKind) { case JsonValueKind.Object: VisitElement(value, prefixStack, data, parserOptions); break; case JsonValueKind.Array: var index = 0; foreach (var arrayElement in value.EnumerateArray()) { if (HasIdentifier(arrayElement, prefixStack, parserOptions, out var identifier)) { prefixStack.Push(identifier.Capitalize()); } else { prefixStack.Push(index.ToString()); index++; } try { VisitValue(arrayElement, prefixStack, data, parserOptions); } finally { prefixStack.Pop(); } } break; case JsonValueKind.Number: case JsonValueKind.String: case JsonValueKind.True: case JsonValueKind.False: case JsonValueKind.Null: var key = string.Join(parserOptions.KeyDelimiter, prefixStack.Reverse()); if (data.ContainsKey(key)) { throw new FormatException($"A duplicate key '{key}' was found."); } data[key] = value.ToString(); break; default: throw new FormatException($"Unsupported JSON token '{value.ValueKind}' was found."); } }
/// <summary> /// Loads the XML data from a stream. /// </summary> /// <param name="stream">The xml stream to read.</param> /// <param name="options">The parser options.</param> public static IDictionary <string, string> ParseStream(Stream stream, ParseToDictionaryOptions options = null) { var parserOptions = options ?? new ParseToDictionaryOptions(); var data = new SortedDictionary <string, string>(StringComparer.OrdinalIgnoreCase); var readerSettings = new XmlReaderSettings() { CloseInput = false, // caller will close the stream DtdProcessing = DtdProcessing.Prohibit, IgnoreComments = true, IgnoreWhitespace = true }; stream.Position = 0; // ReSharper disable once ConvertToUsingDeclaration using (var reader = XmlReader.Create(stream, readerSettings)) { var prefixStack = new Stack <string>(); if (parserOptions.Parents != null) { foreach (var parent in parserOptions.Parents) { prefixStack.Push(parent); } } SkipUntilRootElement(reader); // We process the root element individually since it doesn't contribute to prefix ProcessAttributes(reader, prefixStack, data, AddNamePrefix, parserOptions); ProcessAttributes(reader, prefixStack, data, AddAttributePair, parserOptions); var preNodeType = reader.NodeType; while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: prefixStack.Push(reader.LocalName.Capitalize()); ProcessAttributes(reader, prefixStack, data, AddNamePrefix, parserOptions); ProcessAttributes(reader, prefixStack, data, AddAttributePair, parserOptions); // If current element is self-closing if (reader.IsEmptyElement) { prefixStack.Pop(); } break; case XmlNodeType.EndElement: if (prefixStack.Any()) { // If this EndElement node comes right after an Element node, // it means there is no text/CDATA node in current element if (preNodeType == XmlNodeType.Element) { var key = string.Join(parserOptions.KeyDelimiter, prefixStack.Reverse()); data[key] = string.Empty; } prefixStack.Pop(); } break; case XmlNodeType.CDATA: case XmlNodeType.Text: { var key = string.Join(parserOptions.KeyDelimiter, prefixStack.Reverse()); if (data.ContainsKey(key)) { throw new FormatException($"A duplicate key '{key}' was found. {GetLineInfo(reader)}"); } data[key] = reader.Value; break; } case XmlNodeType.XmlDeclaration: case XmlNodeType.ProcessingInstruction: case XmlNodeType.Comment: case XmlNodeType.Whitespace: // Ignore certain types of nodes break; default: throw new FormatException($"Unsupported node type '{reader.NodeType}' was found. {GetLineInfo(reader)}"); } preNodeType = reader.NodeType; // If this element is a self-closing element, // we pretend that we just processed an EndElement node // because a self-closing element contains an end within itself if (preNodeType == XmlNodeType.Element && reader.IsEmptyElement) { preNodeType = XmlNodeType.EndElement; } } } return(data); }
// Common attributes contribute to key-value pairs // This method adds a key-value pair if current node in reader represents a common attribute private static void AddAttributePair(XmlReader reader, Stack <string> prefixStack, IDictionary <string, string> data, XmlWriter writer, ParseToDictionaryOptions options) { //if (options.IsIndexAttribute(reader.LocalName, prefixStack)) //{ // return; //} prefixStack.Push(reader.LocalName.Capitalize()); var key = string.Join(options.KeyDelimiter, prefixStack.Reverse()); if (data.ContainsKey(key)) { throw new FormatException($"A duplicate key '{key}' was found. {GetLineInfo(reader)}"); } data[key] = reader.Value; prefixStack.Pop(); }
// The special attribute "Name" only contributes to prefix // This method adds a prefix if current node in reader represents a "Name" attribute private static void AddNamePrefix(XmlReader reader, Stack <string> prefixStack, IDictionary <string, string> data, XmlWriter writer, ParseToDictionaryOptions options) { string prefix = null; if (prefixStack.Any()) { prefix = prefixStack.Pop(); } if (!options.IsIdentifier(reader.LocalName, prefixStack)) { if (prefix != null) { prefixStack.Push(prefix); } return; } prefixStack.Push(reader.Value); }
private static void ProcessAttributes(XmlReader reader, Stack <string> prefixStack, IDictionary <string, string> data, Action <XmlReader, Stack <string>, IDictionary <string, string>, XmlWriter, ParseToDictionaryOptions> act, ParseToDictionaryOptions options, XmlWriter writer = null) { for (int i = 0; i < reader.AttributeCount; i++) { reader.MoveToAttribute(i); // If there is a namespace attached to current attribute if (!string.IsNullOrEmpty(reader.NamespaceURI)) { throw new FormatException($"XML namespaces are not supported. {GetLineInfo(reader)}"); } act(reader, prefixStack, data, writer, options); } // Go back to the element containing the attributes we just processed reader.MoveToElement(); }