History

TimeSeriesPlatformLibrary: Added static ParseXmlDataSet to create a DataSet from an XML data set with schema.

J. Ritchie Carroll committed 7 years ago

Commit ed620791 parent 9d5be172 files · +410 −4Browse files

Showing the changes to Source/Libraries/TimeSeriesPlatformLibrary/Data/DataSet.cpp (1 of 2 files). Show all

Unified · Split

Source/Libraries/TimeSeriesPlatformLibrary/Data/DataSet.cpp+386−2
@@ -22,9 +22,26 @@ · 21 unmodified lines
22//******************************************************************************************************22//******************************************************************************************************
23 23
24#include "DataSet.h"24#include "DataSet.h"
25#include "../Common/pugixml.hpp"
25 26
26using namespace std;27using namespace std;
28using namespace pugi;
27using namespace GSF::Data;29using namespace GSF::Data;
30using namespace GSF::TimeSeries;
31
32DataSetException::DataSetException(string message) noexcept :
33 m_message(move(message))
34{
35}
36
37const char* DataSetException::what() const noexcept
38{
39 return &m_message[0];
40}
41
42const string DataSet::XmlSchemaNamespace = "http://www.w3.org/2001/XMLSchema";
43
44const string DataSet::ExtXmlSchemaDataNamespace = "urn:schemas-microsoft-com:xml-msdata";
28 45
29DataSet::DataSet() = default;46DataSet::DataSet() = default;
30 47
@@ -40,11 +57,21 @@ const DataTablePtr& DataSet::Table(const string& tableName) const · 9 unmodified lines
40 return DataTable::NullPtr;57 return DataTable::NullPtr;
41}58}
42 59
43const DataTablePtr& DataSet::operator[](const std::string& tableName) const60const DataTablePtr& DataSet::operator[](const string& tableName) const
44{61{
45 return Table(tableName);62 return Table(tableName);
46}63}
47 64
65DataTablePtr DataSet::CreateTable(const string& name)
66{
67 return NewSharedPtr<DataTable, DataSetPtr, string>(shared_from_this(), name);
68}
69
70int32_t DataSet::TableCount() const
71{
72 return m_tables.size();
73}
74
48void DataSet::IterateTables(TableIteratorHandlerFunction iteratorHandler, void* userData)75void DataSet::IterateTables(TableIteratorHandlerFunction iteratorHandler, void* userData)
49{76{
50 for (auto const& item : m_tables)77 for (auto const& item : m_tables)
@@ -57,7 +84,364 @@ bool DataSet::AddOrUpdateTable(DataTablePtr table) · 6 unmodified lines
57 return m_tables.insert_or_assign(table->Name(), std::move(table)).second;84 return m_tables.insert_or_assign(table->Name(), std::move(table)).second;
58}85}
59 86
60bool DataSet::RemoveTable(const std::string& tableName)87bool DataSet::RemoveTable(const string& tableName)
61{88{
62 return m_tables.erase(tableName) > 0;89 return m_tables.erase(tableName) > 0;
63}90}
91
92DataSetPtr DataSet::ParseXmlDataSet(const vector<uint8_t>& xmlDataSet)
93{
94 xml_document document;
95
96 // Load dataset into an XML parser
97 const xml_parse_result result = document.load_buffer_inplace(const_cast<uint8_t*>(xmlDataSet.data()), xmlDataSet.size());
98
99 if (result.status != xml_parse_status::status_ok)
100 throw DataSetException("Failed to parse dataset XML: " + string(result.description()));
101
102 DataSetPtr dataSet = NewSharedPtr<DataSet>();
103
104 // Find root node
105 xml_node rootNode = document.document_element();
106 const string rootNodeName = string(rootNode.name());
107
108 // Find schema node
109 xml_node schemaNode;
110
111 for (xml_node node = rootNode.first_child(); node; node = node.next_sibling())
112 {
113 const string name = node.name();
114
115 if (!EndsWith(name, "schema", false))
116 continue;
117
118 xml_attribute idAttribute = node.attribute("id");
119
120 if (IsEqual(idAttribute.value(), rootNodeName, false))
121 {
122 schemaNode = node;
123 break;
124 }
125 }
126
127 if (schemaNode == nullptr)
128 throw DataSetException("Failed to parse dataset XML: cannot find schema node for \"" + rootNodeName + "\"");
129
130 string schemaPrefix{}, extSchemaDataPrefix{};
131
132 // Validate schema namespaces
133 for (xml_attribute attr = schemaNode.first_attribute(); attr; attr = attr.next_attribute())
134 {
135 const string name = attr.name();
136 const string value = attr.value();
137
138 if (IsEqual(value, XmlSchemaNamespace) && StartsWith(name, "xmlns:"))
139 schemaPrefix = name.substr(6) + ":";
140
141 if (IsEqual(value, ExtXmlSchemaDataNamespace) && StartsWith(name, "xmlns:"))
142 extSchemaDataPrefix = name.substr(6) + ":";
143 }
144
145 if (schemaPrefix.empty())
146 throw DataSetException("Failed to parse dataset XML: cannot find schema namespace \"" + XmlSchemaNamespace + "\"");
147
148 if (!StartsWith(schemaNode.name(), schemaPrefix, false))
149 throw DataSetException("Failed to parse dataset XML: schema node \"" + string(schemaNode.name()) + "\" prefix is not \"" + schemaPrefix + "\"");
150
151 string extDataTypeAttributeName{};
152
153 if (!extSchemaDataPrefix.empty())
154 extDataTypeAttributeName = extSchemaDataPrefix + "DataType";
155
156 xml_attribute nameAttribute, typeAttribute, extDataTypeAttribute;
157
158 // Find element node
159 xml_node elementNode;
160 const string elementNodeName = schemaPrefix + "element";
161
162 for (xml_node node = schemaNode.first_child(); node; node = node.next_sibling())
163 {
164 const string name = node.name();
165
166 if (!IsEqual(name, elementNodeName, false))
167 continue;
168
169 nameAttribute = node.attribute("name");
170
171 if (nameAttribute && IsEqual(nameAttribute.value(), rootNodeName, false))
172 {
173 elementNode = node;
174 break;
175 }
176 }
177
178 if (elementNode == nullptr)
179 throw DataSetException("Failed to parse dataset XML: cannot find schema element node for \"" + rootNodeName + "\"");
180
181 // Find complex-type node - expected to be first child of element node
182 xml_node complexTypeNode = elementNode.first_child();
183
184 if (complexTypeNode == nullptr)
185 throw DataSetException("Failed to parse dataset XML: cannot find schema element complex-type node for \"" + rootNodeName + "\"");
186
187 if (!IsEqual(complexTypeNode.name(), schemaPrefix + "complexType", false))
188 throw DataSetException("Failed to parse dataset XML: unexpected schema element node child encountered \"" + string(complexTypeNode.name()) + "\", expected \"" + schemaPrefix + "complexType\"");
189
190 // Find choice node - expected to be first child of complex-type node
191 const xml_node choiceNode = complexTypeNode.first_child();
192
193 if (choiceNode == nullptr)
194 throw DataSetException("Failed to parse dataset XML: cannot find schema element complex-type choice node for \"" + rootNodeName + "\"");
195
196 if (!IsEqual(choiceNode.name(), schemaPrefix + "choice", false))
197 throw DataSetException("Failed to parse dataset XML: unexpected schema element complex-type node child encountered \"" + string(choiceNode.name()) + "\", expected \"" + schemaPrefix + "choice\"");
198
199 const xml_attribute maxOccursAttribute = choiceNode.attribute("maxOccurs");
200
201 if (maxOccursAttribute == nullptr)
202 throw DataSetException("Failed to parse dataset XML: cannot find schema element complex-type choice node maxOccurs attribute value for \"" + rootNodeName + "\"");
203
204 if (!IsEqual(maxOccursAttribute.value(), "unbounded", false))
205 throw DataSetException("Failed to parse dataset XML: unexpected schema element complex-type choice node maxOccurs attribute value encountered \"" + string(maxOccursAttribute.value()) + "\", expected \"unbounded\"");
206
207 // Each choice node child element node represents a table definition
208 for (elementNode = choiceNode.first_child(); elementNode; elementNode = elementNode.next_sibling())
209 {
210 if (!IsEqual(elementNode.name(), elementNodeName, false))
211 continue;
212
213 nameAttribute = elementNode.attribute("name");
214
215 if (nameAttribute == nullptr)
216 continue;
217
218 const string tableName = nameAttribute.value();
219
220 if (tableName.empty())
221 continue;
222
223 // Find complex-type node - expected to be first child of element node
224 complexTypeNode = elementNode.first_child();
225
226 if (complexTypeNode == nullptr)
227 continue;
228
229 if (!IsEqual(complexTypeNode.name(), schemaPrefix + "complexType", false))
230 continue;
231
232 // Find sequence node - expected to be first child of complex-type node
233 const xml_node sequenceNode = complexTypeNode.first_child();
234
235 if (sequenceNode == nullptr)
236 continue;
237
238 if (!IsEqual(sequenceNode.name(), schemaPrefix + "sequence", false))
239 continue;
240
241 const DataTablePtr dataTable = NewSharedPtr<DataTable>(dataSet, tableName);
242
243 // Each sequence node child element node represents a table field definition
244 for (xml_node node = sequenceNode.first_child(); node; node = node.next_sibling())
245 {
246 const string name = node.name();
247
248 if (!IsEqual(name, elementNodeName, false))
249 continue;
250
251 nameAttribute = node.attribute("name");
252
253 if (nameAttribute == nullptr)
254 continue;
255
256 const string columnName = nameAttribute.value();
257
258 if (columnName.empty())
259 continue;
260
261 typeAttribute = node.attribute("type");
262
263 if (typeAttribute == nullptr)
264 continue;
265
266 string typeName = typeAttribute.value();
267
268 if (typeName.empty())
269 continue;
270
271 // Remove schema prefix from type name
272 if (StartsWith(typeName, schemaPrefix))
273 typeName = typeName.substr(schemaPrefix.size());
274
275 string extDataTypeName{};
276
277 if (!extDataTypeAttributeName.empty())
278 {
279 extDataTypeAttribute = node.attribute(extDataTypeAttributeName.c_str());
280
281 if (extDataTypeAttribute)
282 extDataTypeName = extDataTypeAttribute.value();
283 }
284
285 DataType columnDataType;
286
287 if (IsEqual(typeName, "string", false))
288 {
289 if (!extDataTypeName.empty() && StartsWith(extDataTypeName, "System.Guid", false))
290 columnDataType = DataType::Guid;
291 else
292 columnDataType = DataType::String;
293 }
294 else if (IsEqual(typeName, "boolean", false))
295 {
296 columnDataType = DataType::Boolean;
297 }
298 else if (IsEqual(typeName, "dateTime", false))
299 {
300 columnDataType = DataType::DateTime;
301 }
302 else if (IsEqual(typeName, "float", false))
303 {
304 columnDataType = DataType::Single;
305 }
306 else if (IsEqual(typeName, "double", false))
307 {
308 columnDataType = DataType::Double;
309 }
310 else if (IsEqual(typeName, "decimal", false))
311 {
312 columnDataType = DataType::Decimal;
313 }
314 else if (IsEqual(typeName, "byte", false))
315 {
316 columnDataType = DataType::Int8;
317 }
318 else if (IsEqual(typeName, "short", false))
319 {
320 columnDataType = DataType::Int16;
321 }
322 else if (IsEqual(typeName, "int", false))
323 {
324 columnDataType = DataType::Int32;
325 }
326 else if (IsEqual(typeName, "long", false))
327 {
328 columnDataType = DataType::Int64;
329 }
330 else if (IsEqual(typeName, "unsignedByte", false))
331 {
332 columnDataType = DataType::UInt8;
333 }
334 else if (IsEqual(typeName, "unsignedShort", false))
335 {
336 columnDataType = DataType::UInt16;
337 }
338 else if (IsEqual(typeName, "unsignedInt", false))
339 {
340 columnDataType = DataType::UInt32;
341 }
342 else if (IsEqual(typeName, "unsignedLong", false))
343 {
344 columnDataType = DataType::UInt64;
345 }
346 else
347 {
348 // Columns with unsupported XMLSchema data types are skipped,
349 // full list here: https://www.w3.org/TR/xmlschema-2/
350 continue;
351 }
352
353 // Create column
354 const DataColumnPtr dataColumn = dataTable->CreateColumn(columnName, columnDataType);
355 dataTable->AddColumn(dataColumn);
356 }
357
358 dataSet->AddOrUpdateTable(dataTable);
359 }
360
361 // Each root node child that matches a table name represents a record
362 for (xml_node recordNode = rootNode.first_child(); recordNode; recordNode = recordNode.next_sibling())
363 {
364 const DataTablePtr dataTable = dataSet->Table(recordNode.name());
365
366 if (dataTable == nullptr)
367 continue;
368
369 const DataRowPtr dataRow = dataTable->CreateRow();
370
371 // Each record node child represents a field value
372 for (xml_node fieldNode = recordNode.first_child(); fieldNode; fieldNode = fieldNode.next_sibling())
373 {
374 const DataColumnPtr dataColumn = dataTable->Column(fieldNode.name());
375
376 if (dataColumn == nullptr)
377 continue;
378
379 const int32_t index = dataColumn->Index();
380 const xml_text value = fieldNode.text();
381
382 switch (dataColumn->Type())
383 {
384 case DataType::String:
385 dataRow->SetStringValue(index, string(value.as_string("")));
386 break;
387 case DataType::Boolean:
388 dataRow->SetBooleanValue(index, value.as_bool());
389 break;
390 case DataType::DateTime:
391 if (value.empty())
392 dataRow->SetNullValue(index);
393 else
394 dataRow->SetDateTimeValue(index, ParseXMLTimestamp(value.as_string()));
395 break;
396 case DataType::Single:
397 dataRow->SetSingleValue(index, value.as_float());
398 break;
399 case DataType::Double:
400 dataRow->SetDoubleValue(index, value.as_double());
401 break;
402 case DataType::Decimal:
403 if (value.empty())
404 dataRow->SetDecimalValue(index, decimal_t(0));
405 else
406 dataRow->SetDecimalValue(index, decimal_t(value.as_string()));
407 break;
408 case DataType::Guid:
409 if (value.empty())
410 dataRow->SetNullValue(index);
411 else
412 dataRow->SetGuidValue(index, ParseGuid(value.as_string()));
413 break;
414 case DataType::Int8:
415 dataRow->SetInt8Value(index, static_cast<int8_t>(value.as_int()));
416 break;
417 case DataType::Int16:
418 dataRow->SetInt16Value(index, static_cast<int16_t>(value.as_int()));
419 break;
420 case DataType::Int32:
421 dataRow->SetInt32Value(index, value.as_int());
422 break;
423 case DataType::Int64:
424 dataRow->SetInt64Value(index, value.as_llong());
425 break;
426 case DataType::UInt8:
427 dataRow->SetUInt8Value(index, static_cast<uint8_t>(value.as_uint()));
428 break;
429 case DataType::UInt16:
430 dataRow->SetUInt16Value(index, static_cast<uint16_t>(value.as_uint()));
431 break;
432 case DataType::UInt32:
433 dataRow->SetUInt32Value(index, value.as_uint());
434 break;
435 case DataType::UInt64:
436 dataRow->SetUInt64Value(index, value.as_ullong());
437 break;
438 default:
439 throw DataSetException("Unexpected column data type encountered");
440 }
441 }
442
443 dataTable->AddRow(dataRow);
444 }
445
446 return dataSet;
447}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t