/// <summary> /// Reads messages asynchronously. /// </summary> /// <param name="reader"> /// The reader. /// </param> /// <returns> /// The messages. /// </returns> internal static async Task <ReadOnlyCollection <Message> > ReadMessagesAsync(XmlReader reader) { var messages = new List <Message>(); if (await reader.MoveToDocumentElementAsync("response").ConfigureAwait(false)) { await reader.ReadAsync().ConfigureAwait(false); reader.EnsureMarkup(XmlNodeType.Element, "messages"); await reader.ReadAsync().ConfigureAwait(false); while (reader.NodeType == XmlNodeType.Element && reader.Name == "msg") { var name = reader.GetRequiredAttribute("type"); var type = EnumConverter <MessageType> .Instance.Convert(name); var text = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false); messages.Add(new Message(type, text)); } reader.EnsureMarkup(XmlNodeType.EndElement, "messages"); } return(new ReadOnlyCollection <Message>(messages)); }
/// <summary> /// Asynchronously reads data into the current /// <see cref="SearchResultStream"/>. /// </summary> /// <param name="reader"> /// The <see cref="XmlReader"/> from which to read. /// </param> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> public async Task ReadXmlAsync(XmlReader reader) { var fieldNames = new List <string>(); this.FieldNames = new ReadOnlyCollection <string>(fieldNames); this.IsFinal = true; if (!await reader.MoveToDocumentElementAsync("results").ConfigureAwait(false)) { return; } string preview = reader.GetRequiredAttribute("preview"); this.IsFinal = !BooleanConverter.Instance.Convert(preview); if (!await reader.ReadAsync().ConfigureAwait(false)) { return; } reader.EnsureMarkup(XmlNodeType.Element, "meta"); await reader.ReadAsync().ConfigureAwait(false); reader.EnsureMarkup(XmlNodeType.Element, "fieldOrder"); if (reader.IsEmptyElement) { await reader.ReadAsync().ConfigureAwait(false); reader.EnsureMarkup(XmlNodeType.EndElement, "meta"); await reader.ReadAsync().ConfigureAwait(false); } else { await reader.ReadEachDescendantAsync("field", async (r) => { await r.ReadAsync().ConfigureAwait(false); var fieldName = await r.ReadContentAsStringAsync().ConfigureAwait(false); fieldNames.Add(fieldName); }).ConfigureAwait(false); await reader.ReadEndElementSequenceAsync("fieldOrder", "meta").ConfigureAwait(false); } if (reader.NodeType == XmlNodeType.Element && reader.Name == "messages") { //// Skip messages await reader.ReadEachDescendantAsync("msg", (r) => { return(Task.FromResult(true)); }).ConfigureAwait(false); reader.EnsureMarkup(XmlNodeType.EndElement, "messages"); await reader.ReadAsync().ConfigureAwait(false); } }
static async Task <dynamic> ParsePropertyValueAsync(XmlReader reader, int level) { if (reader.IsEmptyElement) { await reader.ReadAsync().ConfigureAwait(false); return(null); } string name = reader.Name; dynamic value; await reader.ReadAsync().ConfigureAwait(false); switch (reader.NodeType) { default: value = await reader.ReadContentAsStringAsync().ConfigureAwait(false); break; case XmlNodeType.Element: // TODO: rewrite switch (reader.Name) { case "s:dict": value = await ParseDictionaryAsync(reader, level).ConfigureAwait(false); break; case "s:list": value = await ParseListAsync(reader, level).ConfigureAwait(false); break; default: throw new InvalidDataException(); // TODO: Diagnostics : unexpected start tag } break; case XmlNodeType.EndElement: reader.EnsureMarkup(XmlNodeType.EndElement, name); value = null; break; } reader.EnsureMarkup(XmlNodeType.EndElement, name); await reader.ReadAsync().ConfigureAwait(false); return(value); }
/// <summary> /// Asynchronously positions the source <see cref="XmlReader"/> to the start- /// tag following an <see cref="XmlNodeType"/>.XmlDeclaration. /// </summary> /// <exception cref="XmlException"> /// Thrown when an XML error condition occurs. /// </exception> /// <param name="reader"> /// The source <see cref="XmlReader"/>. /// </param> /// <param name="names"> /// Optional list of permitted document element names. /// </param> /// <returns> /// <c>true</c> if the source <see cref="XmlReader"/> is successfully /// positioned at one of the permitted document element /// <paramref name= "names"/>; otherwise, if the end of file is reached, /// <c>false</c>. /// </returns> /// /// ### <exception cref="ArgumentNullException"> /// <paramref name="reader"/> is <c>null</c>. /// </exception> /// ### <exception cref="InvalidDataException"> /// There is no start-tag following the /// <see cref="XmlNodeType"/>.XmlDeclaration or the start-tag is not in the /// list of permitted document element names. /// </exception> public static async Task <bool> MoveToDocumentElementAsync(this XmlReader reader, params string[] names) { Contract.Requires <ArgumentNullException>(reader != null); if (reader.ReadState == ReadState.Initial) { await reader.ReadAsync().ConfigureAwait(false); if (reader.NodeType == XmlNodeType.XmlDeclaration) { try { await reader.ReadAsync().ConfigureAwait(false); } catch (XmlException) { //// WORKAROUND: //// Issue: Some searches return no results and in //// these cases Splunk writes nothing but an XML //// Declaration. When nothing follows the declaration //// the XmlReader fails to detect EOF, does not update //// the current XmlNode, and then throws an XmlException //// because it thinks the XmlNode appears on a line //// other than the first. //// //// We catch the issue here, dispose the reader to //// ensure that EOF is true, and then return. //// //// Verified against Microsoft .NET 4.5 and Splunk //// 5.0.4, 6.0.3, and 6.1.1. if (reader.NodeType == XmlNodeType.XmlDeclaration) { reader.Dispose(); return(false); } throw; } } } else { reader.MoveToElement(); // ensures we're on an element, not an attribute } if (reader.ReadState != ReadState.Interactive) { return(false); } reader.EnsureMarkup(XmlNodeType.Element, names); return(true); }
/// <summary> /// Reads a sequence of element end tags. /// </summary> /// <remarks> /// The <paramref name="reader"/> should be positioned on the first end tag /// in the sequence of <paramref name="names"/> when this method is called. /// The <paramref name="reader"/> will be positioned on the node following /// the last end tag in the sequence of <paramref name="names "/> when this /// method returns. /// </remarks> /// <param name="reader"> /// The source <see cref="XmlReader"/>. /// </param> /// <param name="names"> /// The sequence of element end tag names to match. /// </param> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> /// /// ### <exception cref="ArgumentNullException"> /// <paramref name="reader"/> or <paramref name="names"/> is <c>null</c>. /// </exception> /// ### <exception cref="InvalidDataException"> /// If the sequence of nodes read from <paramref name="reader"/> does not /// match the sequence of end tag <paramref name="names"/>. /// </exception> public static async Task ReadEndElementSequenceAsync(this XmlReader reader, params string[] names) { Contract.Requires <ArgumentNullException>(reader != null); Contract.Requires <ArgumentNullException>(names != null); foreach (var name in names) { reader.EnsureMarkup(XmlNodeType.EndElement, name); await reader.ReadAsync().ConfigureAwait(false); } }
static async Task <ReadOnlyCollection <dynamic> > ParseListAsync(XmlReader reader, int level) { List <dynamic> value = new List <dynamic>(); if (!reader.IsEmptyElement) { await reader.ReadAsync().ConfigureAwait(false); while (reader.NodeType == XmlNodeType.Element && reader.Name == "s:item") { value.Add(await ParsePropertyValueAsync(reader, level + 1).ConfigureAwait(false)); } reader.EnsureMarkup(XmlNodeType.EndElement, "s:list"); } await reader.ReadAsync().ConfigureAwait(false); return(new ReadOnlyCollection <dynamic>(value)); }
/// <summary> /// Asynchronously reads data into the current <see cref="SearchResult"/>. /// </summary> /// <param name="reader"> /// The <see cref="XmlReader"/> from which to read. /// </param> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> public async Task ReadXmlAsync(XmlReader reader) { Contract.Requires <ArgumentNullException>(reader != null, "reader"); reader.MoveToElement(); reader.EnsureMarkup(XmlNodeType.Element, "result"); this.Object = new ExpandoObject(); var dictionary = (IDictionary <string, object>) this.Object; this.SegmentedRaw = null; await reader.ReadEachDescendantAsync("field", async (r) => { var key = r.GetRequiredAttribute("k"); var values = new List <string>(); var fieldDepth = r.Depth; while (await r.ReadAsync().ConfigureAwait(false)) { if (r.Depth == fieldDepth) { break; } Debug.Assert(r.Depth > fieldDepth, "This loop should have exited earlier."); r.EnsureMarkup(XmlNodeType.Element, "value", "v"); if (r.Name == "value") { if (await r.ReadToDescendantAsync("text").ConfigureAwait(false)) { values.Add(await r.ReadElementContentAsStringAsync().ConfigureAwait(false)); } } else if (r.Name == "v") { Debug.Assert(this.SegmentedRaw == null); Debug.Assert(key == "_raw"); string value = await r.ReadOuterXmlAsync().ConfigureAwait(false); this.SegmentedRaw = XElement.Parse(value); values.Add(this.SegmentedRaw.Value); } } switch (values.Count) { case 0: dictionary.Add(key, null); break; case 1: dictionary.Add(key, values[0]); break; default: dictionary.Add(key, new ReadOnlyCollection <string>(values)); break; } }).ConfigureAwait(false); }
static async Task<dynamic> ParsePropertyValueAsync(XmlReader reader, int level) { if (reader.IsEmptyElement) { await reader.ReadAsync(); return null; } string name = reader.Name; dynamic value; await reader.ReadAsync(); switch (reader.NodeType) { default: value = await reader.ReadContentAsStringAsync(); break; case XmlNodeType.Element: // TODO: rewrite switch (reader.Name) { case "s:dict": value = await ParseDictionaryAsync(reader, level); break; case "s:list": value = await ParseListAsync(reader, level); break; default: throw new InvalidDataException(); // TODO: Diagnostics : unexpected start tag } break; case XmlNodeType.EndElement: reader.EnsureMarkup(XmlNodeType.EndElement, name); value = null; break; } reader.EnsureMarkup(XmlNodeType.EndElement, name); await reader.ReadAsync(); return value; }
static async Task<ReadOnlyCollection<dynamic>> ParseListAsync(XmlReader reader, int level) { List<dynamic> value = new List<dynamic>(); if (!reader.IsEmptyElement) { await reader.ReadAsync(); while (reader.NodeType == XmlNodeType.Element && reader.Name == "s:item") { value.Add(await ParsePropertyValueAsync(reader, level + 1)); } reader.EnsureMarkup(XmlNodeType.EndElement, "s:list"); } await reader.ReadAsync(); return new ReadOnlyCollection<dynamic>(value); }
static async Task<dynamic> ParseDictionaryAsync(XmlReader reader, int level) { var value = (IDictionary<string, dynamic>)new ExpandoObject(); if (!reader.IsEmptyElement) { await reader.ReadAsync(); while (reader.NodeType == XmlNodeType.Element && reader.Name == "s:key") { string name = reader.GetAttribute("name"); // TODO: Include a domain-specific name translation capability (?) if (level == 0) { switch (name) { case "action.email.subject.alert": name = "action.email.subject_alert"; break; case "action.email.subject.report": name = "action.email.subject_report"; break; case "action.email": case "action.populate_lookup": case "action.rss": case "action.script": case "action.summary_index": case "alert.suppress": case "auto_summarize": name += ".IsEnabled"; break; case "alert_comparator": name = "alert.comparator"; break; case "alert_condition": name = "alert.condition"; break; case "alert_threshold": name = "alert.threshold"; break; case "alert_type": name = "alert.type"; break; case "coldPath.maxDataSizeMB": name = "coldPath_maxDataSizeMB"; break; case "display.visualizations.charting.chart": name += ".Type"; break; case "homePath.maxDataSizeMB": name = "homePath_maxDataSizeMB"; break; case "update.checksum.type": name = "update.checksum_type"; break; } } string[] names = name.Split(':', '.'); var dictionary = value; string propertyName; dynamic propertyValue; for (int i = 0; i < names.Length - 1; i++) { propertyName = NormalizePropertyName(names[i]); if (dictionary.TryGetValue(propertyName, out propertyValue)) { if (!(propertyValue is ExpandoObject)) { throw new InvalidDataException(); // TODO: Diagnostics : conversion error } } else { propertyValue = new ExpandoObject(); dictionary.Add(propertyName, propertyValue); } dictionary = (IDictionary<string, object>)propertyValue; } propertyName = NormalizePropertyName(names[names.Length - 1]); propertyValue = await ParsePropertyValueAsync(reader, level + 1); dictionary.Add(propertyName, propertyValue); } reader.EnsureMarkup(XmlNodeType.EndElement, "s:dict"); } await reader.ReadAsync(); return value; // TODO: what's the type seen by dynamic? }
/// <summary> /// Asynchronously reads XML data into the current <see cref="AtomEntry"/>. /// </summary> /// <exception cref="InvalidDataException"> /// Thrown when an Invalid Data error condition occurs. /// </exception> /// <param name="reader"> /// The reader from which to read. /// </param> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> public async Task ReadXmlAsync(XmlReader reader) { Contract.Requires<ArgumentNullException>(reader != null, "reader"); this.Author = null; this.Content = null; this.Id = null; this.Links = null; this.Published = DateTime.MinValue; this.Title = null; this.Updated = DateTime.MinValue; reader.Requires(await reader.MoveToDocumentElementAsync("entry")); Dictionary<string, Uri> links = null; await reader.ReadAsync(); while (reader.NodeType == XmlNodeType.Element) { string name = reader.Name; switch (name) { case "title": this.Title = await reader.ReadElementContentAsync(StringConverter.Instance); break; case "id": this.Id = await reader.ReadElementContentAsync(UriConverter.Instance); break; case "author": await reader.ReadAsync(); reader.EnsureMarkup(XmlNodeType.Element, "name"); this.Author = await reader.ReadElementContentAsync(StringConverter.Instance); reader.EnsureMarkup(XmlNodeType.EndElement, "author"); await reader.ReadAsync(); break; case "published": this.Published = await reader.ReadElementContentAsync(DateTimeConverter.Instance); break; case "updated": this.Updated = await reader.ReadElementContentAsync(DateTimeConverter.Instance); break; case "link": if (links == null) { links = new Dictionary<string, Uri>(); } var href = reader.GetRequiredAttribute("href"); var rel = reader.GetRequiredAttribute("rel"); links[rel] = UriConverter.Instance.Convert(href); await reader.ReadAsync(); break; case "content": this.Content = await ParsePropertyValueAsync(reader, 0); break; default: throw new InvalidDataException(); // TODO: Diagnostics : unexpected start tag } } reader.EnsureMarkup(XmlNodeType.EndElement, "entry"); await reader.ReadAsync(); if (links != null) { this.Links = new ReadOnlyDictionary<string, Uri>(links); } }
/// <summary> /// Asynchronously reads XML data into the current <see cref="AtomFeed"/>. /// </summary> /// <exception cref="InvalidDataException"> /// Thrown when an Invalid Data error condition occurs. /// </exception> /// <param name="reader"> /// The reader from which to read. /// </param> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> public async Task ReadXmlAsync(XmlReader reader) { Contract.Requires <ArgumentNullException>(reader != null, "reader"); this.Author = null; this.Entries = null; this.GeneratorVersion = null; this.Id = null; this.Links = null; this.Messages = null; this.Pagination = Pagination.None; this.Title = null; this.Updated = DateTime.MinValue; reader.Requires(await reader.MoveToDocumentElementAsync("feed").ConfigureAwait(false)); var documentElementName = reader.Name; List <AtomEntry> entries = null; Dictionary <string, Uri> links = null; List <Message> messages = null; await reader.ReadAsync().ConfigureAwait(false); while (reader.NodeType == XmlNodeType.Element) { string name = reader.Name; switch (name) { case "title": this.Title = await reader.ReadElementContentAsync(StringConverter.Instance).ConfigureAwait(false); break; case "id": this.Id = await reader.ReadElementContentAsync(UriConverter.Instance).ConfigureAwait(false); break; case "author": await reader.ReadAsync().ConfigureAwait(false); reader.EnsureMarkup(XmlNodeType.Element, "name"); this.Author = await reader.ReadElementContentAsync(StringConverter.Instance).ConfigureAwait(false); reader.EnsureMarkup(XmlNodeType.EndElement, "author"); await reader.ReadAsync().ConfigureAwait(false); break; case "generator": // string build = reader.GetRequiredAttribute("build"); // TODO: Incorporate build number? Build number sometimes adds a fifth digit. string version = reader.GetRequiredAttribute("version"); this.GeneratorVersion = VersionConverter.Instance.Convert(string.Join(".", version)); await reader.ReadAsync().ConfigureAwait(false); break; case "updated": this.Updated = await reader.ReadElementContentAsync(DateTimeConverter.Instance).ConfigureAwait(false); break; case "entry": var entry = new AtomEntry(); if (entries == null) { entries = new List <AtomEntry>(); } entries.Add(entry); await entry.ReadXmlAsync(reader).ConfigureAwait(false); break; case "link": var href = reader.GetRequiredAttribute("href"); var rel = reader.GetRequiredAttribute("rel"); if (links == null) { links = new Dictionary <string, Uri>(); } links[rel] = UriConverter.Instance.Convert(href); await reader.ReadAsync().ConfigureAwait(false); break; case "s:messages": bool isEmptyElement = reader.IsEmptyElement; await reader.ReadAsync().ConfigureAwait(false); if (messages == null) { messages = new List <Message>(); } if (isEmptyElement) { continue; } while (reader.NodeType == XmlNodeType.Element && reader.Name == "s:msg") { var value = reader.GetRequiredAttribute("type"); var type = EnumConverter <MessageType> .Instance.Convert(value); var text = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false); messages.Add(new Message(type, text)); } if (reader.NodeType == XmlNodeType.EndElement) { reader.EnsureMarkup(XmlNodeType.EndElement, "s:messages"); await reader.ReadAsync().ConfigureAwait(false); } break; case "opensearch:itemsPerPage": int itemsPerPage = await reader.ReadElementContentAsync(Int32Converter.Instance).ConfigureAwait(false); this.Pagination = new Pagination(itemsPerPage, this.Pagination.StartIndex, this.Pagination.TotalResults); break; case "opensearch:startIndex": int startIndex = await reader.ReadElementContentAsync(Int32Converter.Instance).ConfigureAwait(false); this.Pagination = new Pagination(this.Pagination.ItemsPerPage, startIndex, this.Pagination.TotalResults); break; case "opensearch:totalResults": int totalResults = await reader.ReadElementContentAsync(Int32Converter.Instance).ConfigureAwait(false); this.Pagination = new Pagination(this.Pagination.ItemsPerPage, this.Pagination.StartIndex, totalResults); break; default: throw new InvalidDataException(string.Format("Unexpected start tag: {0}", reader.Name)); // TODO: Improved diagnostics } } reader.EnsureMarkup(XmlNodeType.EndElement, documentElementName); await reader.ReadAsync().ConfigureAwait(false); this.Entries = entries == null ? emptyAtomEntryCollection : new ReadOnlyCollection <AtomEntry>(entries); this.Links = links == null ? emptyLinksDictionary : new ReadOnlyDictionary <string, Uri>(links); this.Messages = messages == null ? emptyMessageCollection : new ReadOnlyCollection <Message>(messages); }
/// <summary> /// Asynchronously reads XML data into the current <see cref="AtomFeed"/>. /// </summary> /// <exception cref="InvalidDataException"> /// Thrown when an Invalid Data error condition occurs. /// </exception> /// <param name="reader"> /// The reader from which to read. /// </param> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> public async Task ReadXmlAsync(XmlReader reader) { Contract.Requires<ArgumentNullException>(reader != null, "reader"); this.Author = null; this.Entries = null; this.GeneratorVersion = null; this.Id = null; this.Links = null; this.Messages = null; this.Pagination = Pagination.None; this.Title = null; this.Updated = DateTime.MinValue; reader.Requires(await reader.MoveToDocumentElementAsync("feed")); var documentElementName = reader.Name; List<AtomEntry> entries = null; Dictionary<string, Uri> links = null; List<Message> messages = null; await reader.ReadAsync(); while (reader.NodeType == XmlNodeType.Element) { string name = reader.Name; switch (name) { case "title": this.Title = await reader.ReadElementContentAsync(StringConverter.Instance); break; case "id": this.Id = await reader.ReadElementContentAsync(UriConverter.Instance); break; case "author": await reader.ReadAsync(); reader.EnsureMarkup(XmlNodeType.Element, "name"); this.Author = await reader.ReadElementContentAsync(StringConverter.Instance); reader.EnsureMarkup(XmlNodeType.EndElement, "author"); await reader.ReadAsync(); break; case "generator": // string build = reader.GetRequiredAttribute("build"); // TODO: Incorporate build number? Build number sometimes adds a fifth digit. string version = reader.GetRequiredAttribute("version"); this.GeneratorVersion = VersionConverter.Instance.Convert(string.Join(".", version)); await reader.ReadAsync(); break; case "updated": this.Updated = await reader.ReadElementContentAsync(DateTimeConverter.Instance); break; case "entry": var entry = new AtomEntry(); if (entries == null) { entries = new List<AtomEntry>(); } entries.Add(entry); await entry.ReadXmlAsync(reader); break; case "link": var href = reader.GetRequiredAttribute("href"); var rel = reader.GetRequiredAttribute("rel"); if (links == null) { links = new Dictionary<string, Uri>(); } links[rel] = UriConverter.Instance.Convert(href); await reader.ReadAsync(); break; case "s:messages": bool isEmptyElement = reader.IsEmptyElement; await reader.ReadAsync(); if (messages == null) { messages = new List<Message>(); } if (isEmptyElement) { continue; } while (reader.NodeType == XmlNodeType.Element && reader.Name == "s:msg") { var value = reader.GetRequiredAttribute("type"); var type = EnumConverter<MessageType>.Instance.Convert(value); var text = await reader.ReadElementContentAsStringAsync(); messages.Add(new Message(type, text)); } if (reader.NodeType == XmlNodeType.EndElement) { reader.EnsureMarkup(XmlNodeType.EndElement, "s:messages"); await reader.ReadAsync(); } break; case "opensearch:itemsPerPage": int itemsPerPage = await reader.ReadElementContentAsync(Int32Converter.Instance); this.Pagination = new Pagination(itemsPerPage, this.Pagination.StartIndex, this.Pagination.TotalResults); break; case "opensearch:startIndex": int startIndex = await reader.ReadElementContentAsync(Int32Converter.Instance); this.Pagination = new Pagination(this.Pagination.ItemsPerPage, startIndex, this.Pagination.TotalResults); break; case "opensearch:totalResults": int totalResults = await reader.ReadElementContentAsync(Int32Converter.Instance); this.Pagination = new Pagination(this.Pagination.ItemsPerPage, this.Pagination.StartIndex, totalResults); break; default: throw new InvalidDataException(string.Format("Unexpected start tag: {0}", reader.Name)); // TODO: Improved diagnostics } } reader.EnsureMarkup(XmlNodeType.EndElement, documentElementName); await reader.ReadAsync(); if (entries != null) { this.Entries = new ReadOnlyCollection<AtomEntry>(entries); } if (links != null) { this.Links = new ReadOnlyDictionary<string, Uri>(links); } if (messages != null) { this.Messages = new ReadOnlyCollection<Message>(messages); } }
static async Task <dynamic> ParseDictionaryAsync(XmlReader reader, int level) { var value = (IDictionary <string, dynamic>) new ExpandoObject(); if (!reader.IsEmptyElement) { await reader.ReadAsync().ConfigureAwait(false); while (reader.NodeType == XmlNodeType.Element && reader.Name == "s:key") { string name = reader.GetAttribute("name"); // TODO: Include a domain-specific name translation capability (?) if (level == 0) { switch (name) { case "action.email.subject.alert": name = "action.email.subject_alert"; break; case "action.email.subject.report": name = "action.email.subject_report"; break; case "action.email": case "action.populate_lookup": case "action.rss": case "action.script": case "action.summary_index": case "alert.suppress": case "auto_summarize": name += ".IsEnabled"; break; case "alert_comparator": name = "alert.comparator"; break; case "alert_condition": name = "alert.condition"; break; case "alert_threshold": name = "alert.threshold"; break; case "alert_type": name = "alert.type"; break; case "coldPath.maxDataSizeMB": name = "coldPath_maxDataSizeMB"; break; case "display.visualizations.charting.chart": name += ".Type"; break; case "homePath.maxDataSizeMB": name = "homePath_maxDataSizeMB"; break; case "update.checksum.type": name = "update.checksum_type"; break; } } string[] names = name.Split(':', '.'); var dictionary = value; string propertyName; dynamic propertyValue; for (int i = 0; i < names.Length - 1; i++) { propertyName = NormalizePropertyName(names[i]); if (dictionary.TryGetValue(propertyName, out propertyValue)) { if (!(propertyValue is ExpandoObject)) { throw new InvalidDataException(); // TODO: Diagnostics : conversion error } } else { propertyValue = new ExpandoObject(); dictionary.Add(propertyName, propertyValue); } dictionary = (IDictionary <string, object>)propertyValue; } propertyName = NormalizePropertyName(names[names.Length - 1]); propertyValue = await ParsePropertyValueAsync(reader, level + 1).ConfigureAwait(false); dictionary.Add(propertyName, propertyValue); } reader.EnsureMarkup(XmlNodeType.EndElement, "s:dict"); } await reader.ReadAsync().ConfigureAwait(false); return(value); // TODO: what's the type seen by dynamic? }
/// <summary> /// Asynchronously reads XML data into the current <see cref="AtomEntry"/>. /// </summary> /// <exception cref="InvalidDataException"> /// Thrown when an Invalid Data error condition occurs. /// </exception> /// <param name="reader"> /// The reader from which to read. /// </param> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> public async Task ReadXmlAsync(XmlReader reader) { Contract.Requires <ArgumentNullException>(reader != null, "reader"); this.Author = null; this.Content = null; this.Id = null; this.Links = null; this.Published = DateTime.MinValue; this.Title = null; this.Updated = DateTime.MinValue; reader.Requires(await reader.MoveToDocumentElementAsync("entry").ConfigureAwait(false)); Dictionary <string, Uri> links = null; await reader.ReadAsync().ConfigureAwait(false); while (reader.NodeType == XmlNodeType.Element) { string name = reader.Name; switch (name) { case "title": this.Title = await reader.ReadElementContentAsync(StringConverter.Instance).ConfigureAwait(false); break; case "id": this.Id = await reader.ReadElementContentAsync(UriConverter.Instance).ConfigureAwait(false); break; case "author": await reader.ReadAsync().ConfigureAwait(false); reader.EnsureMarkup(XmlNodeType.Element, "name"); this.Author = await reader.ReadElementContentAsync(StringConverter.Instance).ConfigureAwait(false); reader.EnsureMarkup(XmlNodeType.EndElement, "author"); await reader.ReadAsync().ConfigureAwait(false); break; case "published": this.Published = await reader.ReadElementContentAsync(DateTimeConverter.Instance).ConfigureAwait(false); break; case "updated": this.Updated = await reader.ReadElementContentAsync(DateTimeConverter.Instance).ConfigureAwait(false); break; case "link": if (links == null) { links = new Dictionary <string, Uri>(); } var href = reader.GetRequiredAttribute("href"); var rel = reader.GetRequiredAttribute("rel"); links[rel] = UriConverter.Instance.Convert(href); await reader.ReadAsync().ConfigureAwait(false); break; case "content": this.Content = await ParsePropertyValueAsync(reader, 0).ConfigureAwait(false); break; default: throw new InvalidDataException(); // TODO: Diagnostics : unexpected start tag } } reader.EnsureMarkup(XmlNodeType.EndElement, "entry"); await reader.ReadAsync().ConfigureAwait(false); if (links != null) { this.Links = new ReadOnlyDictionary <string, Uri>(links); } }
/// <summary> /// Asynchronously reads data into the current <see cref="SearchResult"/>. /// </summary> /// <param name="reader"> /// The <see cref="XmlReader"/> from which to read. /// </param> /// <returns> /// A <see cref="Task"/> representing the operation. /// </returns> public async Task ReadXmlAsync(XmlReader reader) { Contract.Requires <ArgumentNullException>(reader != null, "reader"); reader.MoveToElement(); reader.EnsureMarkup(XmlNodeType.Element, "result"); this.Object = new Dictionary <string, object>(); var dictionary = (IDictionary <string, object>) this.Object; this.tagsObject = new Dictionary <string, object>(); var tagsDictionary = (IDictionary <string, object>) this.tagsObject; this.SegmentedRaw = null; await reader.ReadEachDescendantAsync("field", async (r) => { ImmutableSortedSet <string> .Builder tags = null; var key = r.GetRequiredAttribute("k"); var values = new List <object>(); var fieldDepth = r.Depth; while (await r.ReadAsync().ConfigureAwait(false)) { if (r.Depth == fieldDepth) { break; } Debug.Assert(r.Depth > fieldDepth, "This loop should have exited earlier."); r.EnsureMarkup(XmlNodeType.Element, "value", "v"); if (r.IsEmptyElement) { continue; } if (r.Name == "value") { await r.ReadAsync().ConfigureAwait(false); string value = null; while (r.NodeType == XmlNodeType.Element) { r.EnsureMarkup(XmlNodeType.Element, "text", "tag"); var elementName = r.Name; string content = await r.ReadElementContentAsStringAsync().ConfigureAwait(false); switch (elementName) { case "text": value = content; break; case "tag": if (tags == null) { tags = ImmutableSortedSet.CreateBuilder <string>(); } tags.Add(content); break; } } r.EnsureMarkup(XmlNodeType.EndElement, "value"); if (tags != null && tags.Count > 0) { values.Add(new TaggedFieldValue(value ?? string.Empty, tags.ToImmutable())); tags.Clear(); } else { values.Add(value ?? string.Empty); } } else { Debug.Assert(this.SegmentedRaw == null); Debug.Assert(key == "_raw"); string value = await r.ReadOuterXmlAsync().ConfigureAwait(false); this.SegmentedRaw = XElement.Parse(value); values.Add(this.SegmentedRaw.Value); } } if (key.StartsWith("tag::")) { var valueSet = ImmutableSortedSet.ToImmutableSortedSet <string>(values.Cast <string>()); tagsDictionary.Add(key.Substring("tag::".Length), valueSet); dictionary.Add(key, valueSet); } else { switch (values.Count) { case 0: dictionary.Add(key, string.Empty); break; case 1: dictionary.Add(key, values[0]); break; default: dictionary.Add(key, new ReadOnlyCollection <object>(values)); break; } } }).ConfigureAwait(false); }
static async Task <dynamic> ParseDictionaryAsync(XmlReader reader, int level) { var value = (IDictionary <string, dynamic>) new ExpandoObject(); if (!reader.IsEmptyElement) { await reader.ReadAsync().ConfigureAwait(false); while (reader.NodeType == XmlNodeType.Element && reader.Name == "s:key") { string name = reader.GetAttribute("name"); // TODO: Include a domain-specific name translation capability (?) if (level == 0) { switch (name) { case "action.email.subject.alert": name = "action.email.subject_alert"; break; case "action.email.subject.report": name = "action.email.subject_report"; break; case "action.email": case "action.populate_lookup": case "action.rss": case "action.script": case "action.summary_index": case "action.webhook": case "alert.suppress": case "auto_summarize": name += ".IsEnabled"; break; case "alert_comparator": name = "alert.comparator"; break; case "alert_condition": name = "alert.condition"; break; case "alert_threshold": name = "alert.threshold"; break; case "alert_type": name = "alert.type"; break; case "coldPath.maxDataSizeMB": name = "coldPath_maxDataSizeMB"; break; case "display.visualizations.charting.chart": name += ".Type"; break; case "display.visualizations.charting.layout.splitSeries.allowIndependentYRanges": name = "display.visualizations.charting.layout.splitSeries_allowIndependentYRanges"; break; case "homePath.maxDataSizeMB": name = "homePath_maxDataSizeMB"; break; case "update.checksum.type": name = "update.checksum_type"; break; } } string[] names = name.Split(':', '.'); var dictionary = value; string propertyName; dynamic propertyValue; // There are cases where the server sends us bad values for "s:key", namely // the empty string. This happens for example when we get back the metadata for // a search job which contains an "eval". In these cases, we simply replace the // empty string with a literal string called "empty", so that we know where it came // from. // // The risk with this fix is that we will have multiple empty keys at the same // level, and thus using "empty" would clash. However, this would be an even more // serious error on the part of the API, as it would mean we have no way to disambiguate // those two entries. As such, we feel it is safe. if (names[names.Length - 1] == "") { names[names.Length - 1] = "empty"; } for (int i = 0; i < names.Length - 1; i++) { propertyName = NormalizePropertyName(names[i]); if (dictionary.TryGetValue(propertyName, out propertyValue)) { if (!(propertyValue is ExpandoObject)) { throw new InvalidDataException(name); // TODO: Diagnostics : conversion error } } else { propertyValue = new ExpandoObject(); dictionary.Add(propertyName, propertyValue); } dictionary = (IDictionary <string, object>)propertyValue; } propertyName = NormalizePropertyName(names[names.Length - 1]); propertyValue = await ParsePropertyValueAsync(reader, level + 1).ConfigureAwait(false); dictionary.Add(propertyName, propertyValue); } reader.EnsureMarkup(XmlNodeType.EndElement, "s:dict"); } await reader.ReadAsync().ConfigureAwait(false); return(value); // TODO: what's the type seen by dynamic? }