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
2222 //******************************************************************************************************
2323
2424 #include "DataSet.h"
25+#include "../Common/pugixml.hpp"
2526
2627 using namespace std;
28+using namespace pugi;
2729 using namespace GSF::Data;
30+using namespace GSF::TimeSeries;
31+
32+DataSetException::DataSetException(string message) noexcept :
33+ m_message(move(message))
34+{
35+}
36+
37+const char* DataSetException::what() const noexcept
38+{
39+ return &m_message[0];
40+}
41+
42+const string DataSet::XmlSchemaNamespace = "http://www.w3.org/2001/XMLSchema";
43+
44+const string DataSet::ExtXmlSchemaDataNamespace = "urn:schemas-microsoft-com:xml-msdata";
2845
2946 DataSet::DataSet() = default;
3047
@@ -40,11 +57,21 @@ const DataTablePtr& DataSet::Table(const string& tableName) const · 9 unmodified lines
4057 return DataTable::NullPtr;
4158 }
4259
43−const DataTablePtr& DataSet::operator[](const std::string& tableName) const
60+const DataTablePtr& DataSet::operator[](const string& tableName) const
4461 {
4562 return Table(tableName);
4663 }
4764
65+DataTablePtr DataSet::CreateTable(const string& name)
66+{
67+ return NewSharedPtr<DataTable, DataSetPtr, string>(shared_from_this(), name);
68+}
69+
70+int32_t DataSet::TableCount() const
71+{
72+ return m_tables.size();
73+}
74+
4875 void DataSet::IterateTables(TableIteratorHandlerFunction iteratorHandler, void* userData)
4976 {
5077 for (auto const& item : m_tables)
@@ -57,7 +84,364 @@ bool DataSet::AddOrUpdateTable(DataTablePtr table) · 6 unmodified lines
5784 return m_tables.insert_or_assign(table->Name(), std::move(table)).second;
5885 }
5986
60−bool DataSet::RemoveTable(const std::string& tableName)
87+bool DataSet::RemoveTable(const string& tableName)
6188 {
6289 return m_tables.erase(tableName) > 0;
6390 }
91+
92+DataSetPtr 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