Files
ThirdParty-6/ParaView-5.0.1/ParaViewCore/ServerManager/Core/vtkSMStateVersionController.cxx

655 lines
22 KiB
C++

/*=========================================================================
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 <set>
#include <string>
#include <sstream>
#include <vector>
#include <vtk_pugixml.h>
#include <sstream>
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<std::string> 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 << "<Property name=\"VolumeArrays\" >\n";
int index=0;
for (std::set<std::string>::const_iterator it = selected_arrays.begin();
it != selected_arrays.end(); ++it, ++index)
{
stream << " <Element index=\"" << index << "\" value=\"" << (*it).c_str() << "\"/>\n";
}
stream << "</Property>\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 << "<Property name=\"ColorArrayName\" number_of_elements=\"5\">\n"
<< " <Element index=\"0\" value=\"\" />\n"
<< " <Element index=\"1\" value=\"\" />\n"
<< " <Element index=\"2\" value=\"\" />\n"
<< " <Element index=\"3\" value=\"" << attributeType.c_str() << "\" />\n"
<< " <Element index=\"4\" value=\"" << colorArrayName.c_str()<< "\" />\n"
<< "</Property>\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<vtkPVXMLParser> 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 <max;cc++)
{
root->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);
}