/*========================================================================= Program: ParaView Module: vtkSMStateVersionController.cxx Copyright (c) Kitware, Inc. All rights reserved. See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkSMStateVersionController.h" // Don't include vtkAxis. Cannot add dependency on vtkChartsCore in // vtkPVServerManagerCore. // #include "vtkAxis.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkPVXMLElement.h" #include "vtkPVXMLParser.h" #include #include #include #include #include #include using namespace pugi; using namespace std; namespace { string toString(int i) { ostringstream ostr; ostr << i; return ostr.str(); } class vtkSMVersion { public: int Major; int Minor; int Patch; vtkSMVersion(int major, int minor, int patch): Major(major), Minor(minor), Patch(patch) { } bool operator < (const vtkSMVersion& other) const { if (this->Major == other.Major) { if (this->Minor == other.Minor) { return this->Patch < other.Patch; } else { return this->Minor < other.Minor; } } else { return this->Major < other.Major; } } }; //=========================================================================== // Helper functions //=========================================================================== void PurgeElements(const pugi::xpath_node_set &elements_of_interest) { for (pugi::xpath_node_set::const_iterator iter = elements_of_interest.begin(); iter != elements_of_interest.end(); ++iter) { pugi::xml_node node = iter->node(); node.parent().remove_child(node); } } void PurgeElement(const pugi::xml_node& node) { node.parent().remove_child(node); } //=========================================================================== bool Process_4_0_to_4_1(pugi::xml_document &document) { //------------------------------------------------------------------------- // For CTHPart filter, we need to convert AddDoubleVolumeArrayName, // AddUnsignedCharVolumeArrayName and AddFloatVolumeArrayName to a single // VolumeArrays property. The type specific separation is no longer // needed. //------------------------------------------------------------------------- pugi::xpath_node_set cthparts = document.select_nodes( "//ServerManagerState/Proxy[@group='filters' and @type='CTHPart']"); for (pugi::xpath_node_set::const_iterator iter = cthparts.begin(); iter != cthparts.end(); ++iter) { std::set selected_arrays; pugi::xml_node cthpart_node = iter->node(); pugi::xpath_node_set elements_of_interest = cthpart_node.select_nodes( "//Property[@name='AddDoubleVolumeArrayName' " "or @name='AddFloatVolumeArrayName' " "or @name='AddUnsignedCharVolumeArrayName']" "/Element[@value]"); for (pugi::xpath_node_set::const_iterator iter2 = elements_of_interest.begin(); iter2 != elements_of_interest.end(); ++iter2) { selected_arrays.insert( iter2->node().attribute("value").value()); } // Remove all of those old property elements. PurgeElements(cthpart_node.select_nodes( "//Property[@name='AddDoubleVolumeArrayName' " "or @name='AddFloatVolumeArrayName' " "or @name='AddUnsignedCharVolumeArrayName']")); // Add new XML state for "VolumeArrays" property with the value as // "selected_arrays". std::ostringstream stream; stream << "\n"; int index=0; for (std::set::const_iterator it = selected_arrays.begin(); it != selected_arrays.end(); ++it, ++index) { stream << " \n"; } stream << "\n"; std::string buffer = stream.str(); if (!cthpart_node.append_buffer(buffer.c_str(), buffer.size())) { abort(); } } //------------------------------------------------------------------------- return true; } //=========================================================================== struct Process_4_1_to_4_2 { static const char* const NAME; static const char* const VALUE; bool operator()(xml_document &document) { bool ret = ConvertRepresentationColorArrayName(document) && ConvertChartNodes(document) && ConvertGlyphFilter(document); return ret; } static bool ConvertRepresentationColorArrayName(xml_document &document) { //------------------------------------------------------------------------- // Convert ColorArrayName and ColorAttributeType properties to new style. // Since of separate properties, we now have just 1 ColorArrayName property // with 5 elements. //------------------------------------------------------------------------- // Find all representations. pugi::xpath_node_set representation_elements = document.select_nodes("//ServerManagerState/Proxy[@group='representations']"); for (pugi::xpath_node_set::const_iterator iter = representation_elements.begin(); iter != representation_elements.end(); ++iter) { // select ColorAttributeType and ColorArrayName properties for each // representation. std::string colorArrayName; if (pugi::xpath_node nameElement = iter->node().select_single_node( "//Property[@name='ColorArrayName' and @number_of_elements='1']" "/Element[@index='0' and @value]")) { colorArrayName = nameElement.node().attribute("value").value(); // remove the "Property" xml-element PurgeElement(nameElement.node().parent()); } std::string attributeType(""); if (pugi::xpath_node typeElement = iter->node().select_single_node( "//Property[@name='ColorAttributeType' and @number_of_elements='1']" "/Element[@index='0' and @value]")) { attributeType = typeElement.node().attribute("value").value(); // remove the "Property" xml-element PurgeElement(typeElement.node().parent()); } if (!colorArrayName.empty() || !attributeType.empty()) { std::ostringstream stream; stream << "\n" << " \n" << " \n" << " \n" << " \n" << " \n" << "\n"; std::string buffer = stream.str(); if (!iter->node().append_buffer(buffer.c_str(), buffer.size())) { abort(); } } } //------------------------------------------------------------------------- // remove camera manipulator proxies and interactorstyles proxies. //------------------------------------------------------------------------- PurgeElements(document.select_nodes( "//ServerManagerState/Proxy[@group='interactorstyles' or @group='cameramanipulators']")); //------------------------------------------------------------------------- // convert global property links //------------------------------------------------------------------------- pugi::xml_node smstate = document.root().child("ServerManagerState"); pugi::xml_node links = smstate.child("Links"); if (!links) { links = smstate.append_child("Links"); } pugi::xpath_node_set global_property_links = document.select_nodes("//ServerManagerState/GlobalPropertiesManagers/GlobalPropertiesManager/Link"); for (pugi::xpath_node_set::const_iterator iter = global_property_links.begin(); iter != global_property_links.end(); ++iter) { pugi::xml_node linkNode = iter->node(); linkNode.set_name("GlobalPropertyLink"); links.append_copy(linkNode); } //------------------------------------------------------------------------- return true; } static bool ConvertChartNodes(xml_document &document) { const char* axisProperties3[] = {"AxisColor", "AxisGridColor", "AxisLabelColor", "AxisTitleColor"}; const char* rangeProperties1[] = {"LeftAxisRange", "BottomAxisRange", "RightAxisRange", "TopAxisRange"}; const char* axisProperties4[] = {"AxisLabelFont", "AxisTitleFont"}; const char* fontProperties1[] = {"ChartTitleFont", // next properties are generated from the // previous array axisProperties4[] "LeftAxisLabelFont", "BottomAxisLabelFont", "RightAxisLabelFont", "TopAxisLabelFont", "LeftAxisTitleFont", "BottomAxisTitleFont", "RightAxisTitleFont", "TopAxisTitleFont"}; const char* axisProperties1[] = {"AxisLabelNotation", "AxisLabelPrecision", "AxisLogScale", "AxisTitle", "AxisUseCustomLabels", "AxisUseCustomRange", "ShowAxisGrid", "ShowAxisLabels"}; return ConvertChartNode(document, axisProperties3, sizeof(axisProperties3)/sizeof(axisProperties3[0]), 3, 4, validOutputForXYChart, axisNodeName) && ConvertChartNode(document, rangeProperties1, sizeof(rangeProperties1)/sizeof(rangeProperties1[0]), 1, 2, validOutputTrue, rangeNodeName) && // the order of execution of the next two functions is important ConvertChartNode(document, axisProperties4, sizeof(axisProperties4)/sizeof(axisProperties4[0]), 4, 4, validOutputForXYChart, axisNodeName) && ConvertChartNode(document, fontProperties1, sizeof(fontProperties1)/sizeof(fontProperties1[0]), 1, 4, validOutputTrue, fontNodeName) && ConvertChartNode(document, axisProperties1, sizeof(axisProperties1)/sizeof(axisProperties1[0]), 1, 4, validOutputForXYChart, axisNodeName); } //--------------------------------------------------------------------------- static bool ConvertChartNode( xml_document &document, const char* property[], int numberOfProperties, int numberOfComponents, int numberOfOutputs, bool (*validOutput)(xml_node viewNode, int outputIndex), string (*nodeName) (xml_node oldNode, int outputIndex)) { if (numberOfProperties <= 0) { return true; } string query("//ServerManagerState/Proxy[@group='views']/" "Property[@name='"); query = query + property[0] + "'";; for (int i = 1; i < numberOfProperties; ++i) { query = query + "or @name='" + property[i] + "'"; } query += "]"; xpath_node_set nodes = document.select_nodes(query.c_str()); for (xpath_node_set::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { xml_node oldNode = it->node(); xml_node viewNode = oldNode.parent(); for (int i = 0; i < numberOfOutputs; ++i) { if (validOutput (viewNode, i)) { CreateChartPropertyNode(viewNode, oldNode, numberOfComponents, i, nodeName); } } viewNode.remove_child(oldNode); } return true; } static void CreateChartPropertyNode( xml_node& viewNode, const xml_node& oldNode, int numberOfComponents, int outputIndex, string (*nodeName) (xml_node oldNode, int outputIndex)) { string newNameAttr = nodeName(oldNode, outputIndex); xml_node newNode = CreatePropertyNode ( viewNode, oldNode, newNameAttr, numberOfComponents); for (int i = 0; i < numberOfComponents; ++i) { CreateChartElementNode(newNode, oldNode, numberOfComponents, outputIndex, i); } } static void CreateChartElementNode( xml_node& newNode, const xml_node& oldNode, int numberOfComponents, int outputIndex, int componentIndex) { xml_node oldElement = oldNode.select_single_node( (string("Element[@index='") + toString(outputIndex * numberOfComponents + componentIndex) + "']").c_str()).node(); const char* value = oldElement.attribute(VALUE).value(); CreateElementNode(newNode, componentIndex, value); } //--------------------------------------------------------------------------- static string rangeNodeName(xml_node oldNode, int i) { return string(oldNode.attribute(NAME).value()) + rangeString(i); } static string axisNodeName(xml_node oldNode, int i) { return string(axisString(i)) + oldNode.attribute(NAME).value(); } static string fontNodeName(xml_node oldNode, int i) { string oldNodeName = oldNode.attribute(NAME).value(); switch (i) { case 0: return oldNodeName + "Family"; case 1: return oldNodeName + "Size"; case 2: return replaceFont(&oldNodeName, "Bold"); case 3: return replaceFont(&oldNodeName, "Italic"); default: return oldNodeName; } } static string replaceFont(string* oldNodeName, const char*modifier) { const char* FONT = "Font"; const size_t FONT_LENGTH = 4; size_t pos = oldNodeName->find(FONT); if (pos != string::npos) { return oldNodeName->replace(pos, FONT_LENGTH, modifier); } else return *oldNodeName; } static const char* rangeString (int index) { switch (index) { case 0: return "Minimum"; case 1: return "Maximum"; } return ""; } static const char* axisString (int axis) { switch (axis) { case 0: // case vtkAxis::LEFT: return "Left"; case 1: // case vtkAxis::BOTTOM: return "Bottom"; case 2: // case vtkAxis::RIGHT: return "Right"; case 3: // case vtkAxis::TOP: return "Top"; } return ""; } static bool validOutputTrue(xml_node viewNode, int i) { (void)viewNode; (void)i; return true; } static bool validOutputForXYChart(xml_node viewNode, int i) { const char* const TYPE = "type"; const char* const XY_CHART_VIEW = "XYChartView"; if (i < 0 || i >= 4) { return false; } if (i < 2) { // left, bottom return true; } else if (string(viewNode.attribute(TYPE).value()) == string(XY_CHART_VIEW)) { // return true; } else { return false; } } static void CreateElementNode(xml_node& node, int index, const string& value) { const char* const ELEMENT = "Element"; const char* const INDEX = "index"; xml_node newElement = node.append_child(); newElement.set_name(ELEMENT); xml_attribute a = newElement.append_attribute(INDEX); a.set_value (index); a = newElement.append_attribute(VALUE); a.set_value(value.c_str()); } static xml_node CreatePropertyNode(xml_node& viewNode, const xml_node& oldNode, const string& name, int numberOfElements) { const char* const PROPERTY = "Property"; const char* const NUMBER_OF_ELEMENTS = "number_of_elements"; const char* const ID = "id"; xml_node newNode = viewNode.insert_child_before(node_element, oldNode); newNode.set_name (PROPERTY); xml_attribute a = newNode.append_attribute(NAME); a.set_value(name.c_str()); string newNodeId = string(viewNode.attribute(ID).value()) + "." + name; a = newNode.append_attribute(ID); a.set_value(newNodeId.c_str()); a = newNode.append_attribute(NUMBER_OF_ELEMENTS); a.set_value(numberOfElements); return newNode; } static bool ConvertGlyphFilter(xml_document& document) { bool warn = false; //------------------------------------------------------------------------- // Convert "Glyph" and "ArbitrarySourceGlyph" to "LegacyGlyph" and // "LegacyArbitrarySourceGlyph". We don't explicitly convert those filters to the // new ones, we just create the old proxies for now. //------------------------------------------------------------------------- pugi::xpath_node_set glyph_elements = document.select_nodes( "//ServerManagerState/Proxy[@group='filters' and @type='Glyph']"); for (pugi::xpath_node_set::const_iterator iter = glyph_elements.begin(); iter != glyph_elements.end(); ++iter) { // It's possible that we are using a development version's state file in // which case we don't want to change it. if (iter->node().select_single_node("//Property[@name='GlyphMode']")) { continue; } iter->node().attribute("type").set_value("LegacyGlyph"); warn = true; } glyph_elements = document.select_nodes( "//ServerManagerState/Proxy[@group='filters' and @type='ArbitrarySourceGlyph']"); for (pugi::xpath_node_set::const_iterator iter = glyph_elements.begin(); iter != glyph_elements.end(); ++iter) { iter->node().attribute("type").set_value("LegacyArbitrarySourceGlyph"); warn = true; } if (warn) { vtkGenericWarningMacro("The state file uses the old 'Glyph' filter implementation." "The implementation has changed considerably in ParaView 4.2. " "Consider replacing the Glyph filter with a new Glyph filter. The old implementation " "is still available as 'Legacy Glyph' and will be used for loading this state file."); } return true; } }; const char* const Process_4_1_to_4_2::NAME = "name"; const char* const Process_4_1_to_4_2::VALUE = "value"; }; vtkStandardNewMacro(vtkSMStateVersionController); //---------------------------------------------------------------------------- vtkSMStateVersionController::vtkSMStateVersionController() { } //---------------------------------------------------------------------------- vtkSMStateVersionController::~vtkSMStateVersionController() { } //---------------------------------------------------------------------------- bool vtkSMStateVersionController::Process(vtkPVXMLElement* parent) { vtkPVXMLElement* root = parent; if (parent && strcmp(parent->GetName(), "ServerManagerState") != 0) { root = root->FindNestedElementByName("ServerManagerState"); } if (!root || strcmp(root->GetName(), "ServerManagerState") != 0) { vtkErrorMacro("Invalid root element. Expected \"ServerManagerState\""); return false; } vtkSMVersion version(0, 0, 0); if (const char* str_version = root->GetAttribute("version")) { int v[3]; sscanf(str_version, "%d.%d.%d", &v[0], &v[1], &v[2]); version = vtkSMVersion(v[0], v[1], v[2]); } bool status = true; if (version < vtkSMVersion(4, 0, 1)) { vtkWarningMacro("State file version is less than 4.0." "We will try to load the state file. It's recommended, however, " "that you load the state in ParaView 4.0.1 (or 4.1.0) and save a newer version " "so that it can be loaded more faithfully. " "Loading state files generated from ParaView versions older than 4.0.1 " "is no longer supported."); version = vtkSMVersion(4, 0, 1); } // A little hackish for now, convert vtkPVXMLElement to string. std::ostringstream stream; root->PrintXML(stream, vtkIndent()); // parse using pugi. pugi::xml_document document; if (!document.load(stream.str().c_str())) { vtkErrorMacro("Failed to convert from vtkPVXMLElement to pugi::xml_document"); return false; } if (status && version < vtkSMVersion(4, 1, 0)) { status = Process_4_0_to_4_1(document); version = vtkSMVersion(4, 1, 0); } if (status && version < vtkSMVersion(4, 2, 0)) { status = Process_4_1_to_4_2()(document); version = vtkSMVersion(4, 2, 0); } if (status) { std::ostringstream stream2; document.save(stream2, " "); vtkNew parser; if (parser->Parse(stream2.str().c_str())) { root->RemoveAllNestedElements(); vtkPVXMLElement* newRoot = parser->GetRootElement(); newRoot->CopyAttributesTo(root); for (unsigned int cc=0, max=newRoot->GetNumberOfNestedElements(); cc AddNestedElement(newRoot->GetNestedElement(cc)); } } else { vtkErrorMacro("Internal error: Error parsing converted XML state file."); } } return status; } //---------------------------------------------------------------------------- void vtkSMStateVersionController::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); }