This repository was archived by the owner on Sep 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathResultsReaderXml.cs
305 lines (273 loc) · 11.1 KB
/
ResultsReaderXml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/*
* Copyright 2013 Splunk, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"): you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
namespace Splunk
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
/// <summary>
/// The <see cref="ResultsReaderXml"/> class represents a streaming XML
/// reader for Splunk search results. When a stream from an export search
/// is passed to this reader, it skips any preview events in the stream.
/// If you want to access the preview events, use the
/// <see cref="MultiResultsReaderXml"/> class.
/// </summary>
public class ResultsReaderXml : ResultsReader
{
/// <summary>
/// Initializes a new instance of the <see cref="ResultsReaderXml"/>
/// class for the event stream. You should only attempt to parse an XML
/// stream with the XML reader.
/// </summary>
/// <param name="stream">The stream to parse.</param>
public ResultsReaderXml(Stream stream) :
this(stream, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ResultsReaderXml"/>
/// class.
/// </summary>
/// <param name="stream">The XML stream to parse.</param>
/// <param name="isInMultiReader">
/// Whether the reader is the underlying reader of a multi
/// reader.
/// </param>
internal ResultsReaderXml(Stream stream, bool isInMultiReader) :
base(stream, isInMultiReader)
{
var setting = new XmlReaderSettings
{
ConformanceLevel = ConformanceLevel.Fragment,
};
this.XmlReader = XmlReader.Create(stream, setting);
this.FinishInitialization();
}
/// <summary>
/// Gets the underlying reader of the XML stream.
/// </summary>
internal XmlReader XmlReader
{
get;
private set;
}
/// <summary>
/// Advances to the next set, skipping any remaining event(s)
/// in the current set. Reads metadata before the first event in the
/// next result set.
/// </summary>
/// <returns>Returns false if the end is reached.</returns>
internal override bool AdvanceStreamToNextSet()
{
return this.ReadIntoNextResultsElement();
}
/// <summary>
/// Releases unmanaged resources.
/// </summary>
public override void Dispose()
{
((IDisposable)this.XmlReader).Dispose();
}
/// <summary>
/// Reads to next <b>results</b> element, parses out
/// <see cref="ResultsReader.IsPreview"/> and <see
/// cref="ResultsReader.Fields"/>.
/// </summary>
/// <returns>Returns false if the end is reached.</returns>
private bool ReadIntoNextResultsElement()
{
// Below is an example of an input stream, with a single 'results'
// element. With a stream from an export point, there can be
// multiple ones.
//
// <?xml version='1.0' encoding='UTF-8'?>
// <results preview='0'>
// <meta>
// <fieldOrder>
// <field>series</field>
// <field>sum(kb)</field>
// </fieldOrder>
// </meta>
// <messages>
// <msg type='DEBUG'>base lispy: [ AND ]</msg>
// <msg type='DEBUG'>search context: user='admin', app='search', bs-pathname='/some/path'</msg>
// </messages>
// <result offset='0'>
// <field k='series'>
// <value><text>twitter</text></value>
// </field>
// <field k='sum(kb)'>
// <value><text>14372242.758775</text></value>
// </field>
// </result>
// <result offset='1'>
// <field k='series'>
// <value><text>splunkd</text></value>
// </field>
// <field k='sum(kb)'>
// <value><text>267802.333926</text></value>
// </field>
// </result>
// </results>
if (this.XmlReader.ReadToFollowing("results"))
{
this.IsPreview = XmlConvert.ToBoolean(this.XmlReader["preview"]);
this.ReadMetaElement();
return true;
}
return false;
}
/// <summary>
/// Reads the <b>meta</b> element to populate the
/// <see cref="ResultsReader.Fields"/>
/// property, and moves to its end tag.
/// </summary>
private void ReadMetaElement()
{
if (this.XmlReader.ReadToDescendant("meta"))
{
if (this.XmlReader.ReadToDescendant("fieldOrder"))
{
this.ReadEachDescendant(
"field",
() =>
{
this.Fields.Add(this.XmlReader.ReadElementContentAsString());
});
this.XmlReader.Skip();
}
this.XmlReader.Skip();
}
}
/// <summary>
/// Reads each descendant found and positions the reader on the end
/// tag of the current node.
/// </summary>
/// <param name="name">Name of the descendant.</param>
/// <param name="readAction">
/// The action that reads each descendant found, and
/// positions the reader at the decendant's element depth
/// (for instance, the end tag or the start tag).
/// </param>
private void ReadEachDescendant(string name, Action readAction)
{
if (this.XmlReader.ReadToDescendant(name))
{
readAction();
while (this.XmlReader.ReadToNextSibling(name))
{
readAction();
}
}
}
/// <summary>
/// Returns an enumerator over a set of the events
/// in the event stream, and gets ready for the next set.
/// </summary>
/// <remarks>
/// <para>
/// When using 'search/jobs/export endpoint', search results
/// will be streamed back as they become available. It is possible
/// for one or more previews to be received before the final one.
/// The enumerator returned will be over a single preview or
/// the final results. Each time this method is called,
/// the next preview or the final results are enumerated if they are
/// available; otherwise, an exception is thrown.
/// </para>
/// <para>
/// After all events in the set is enumerated, the metadata of the
/// next set (if available) is read, with
/// <see cref="ResultsReader.IsPreview"/>
/// and <see cref="ResultsReader.Fields"/> being set accordingly.
/// </para>
/// </remarks>
/// <returns>A enumerator.</returns>
internal override IEnumerable<Event> GetEventsFromCurrentSet()
{
while (true)
{
if (!this.XmlReader.ReadToNextSibling("result"))
{
yield break;
}
var result = new Event();
this.ReadEachDescendant(
"field",
() =>
{
var key = this.XmlReader["k"];
if (key == null)
{
throw new XmlException(
"'field' attribute 'k' not found");
}
var values = new List<string>();
var xmlDepthField = this.XmlReader.Depth;
while (this.XmlReader.Read())
{
if (this.XmlReader.Depth == xmlDepthField)
{
break;
}
Debug.Assert(
XmlReader.Depth > xmlDepthField,
"The loop should have exited earlier.");
if (this.XmlReader.IsStartElement("value"))
{
if (this.XmlReader.ReadToDescendant("text"))
{
values.Add(
this.XmlReader.ReadElementContentAsString());
}
}
else if (this.XmlReader.IsStartElement("v"))
{
result.SegmentedRaw = this.XmlReader.ReadOuterXml();
var value = ReadTextContentFromXml(
result.SegmentedRaw);
values.Add(value);
}
}
result.Add(key, new Event.FieldValue(values.ToArray()));
});
yield return result;
}
}
/// <summary>
/// Extracts and concatenate text, excluding any markup.
/// </summary>
/// <param name="xml">The XML fragment with markup.</param>
/// <returns>Extracted and concatenated text.</returns>
private static string ReadTextContentFromXml(string xml)
{
var ret = new StringBuilder();
var stringReader = new StringReader(xml);
var setting = new XmlReaderSettings
{
ConformanceLevel = ConformanceLevel.Fragment,
};
var xmlReader = XmlReader.Create(stringReader, setting);
while (xmlReader.Read())
{
ret.Append(xmlReader.ReadString());
}
return ret.ToString();
}
}
}