1 /*=========================================================================
2
3 Program: ParaView
4 Module: vtkSelection.cxx
5
6 Copyright (c) Kitware, Inc.
7 All rights reserved.
8 See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.
9
10 This software is distributed WITHOUT ANY WARRANTY; without even
11 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12 PURPOSE. See the above copyright notice for more information.
13
14 =========================================================================*/
15 #include "vtkSelection.h"
16
17 #include "vtkAbstractArray.h"
18 #include "vtkAtomicTypes.h"
19 #include "vtkFieldData.h"
20 #include "vtkInformation.h"
21 #include "vtkInformationIntegerKey.h"
22 #include "vtkInformationIterator.h"
23 #include "vtkInformationObjectBaseKey.h"
24 #include "vtkInformationStringKey.h"
25 #include "vtkInformationVector.h"
26 #include "vtkNew.h"
27 #include "vtkObjectFactory.h"
28 #include "vtkSMPTools.h"
29 #include "vtkSelectionNode.h"
30 #include "vtkSignedCharArray.h"
31 #include "vtkTable.h"
32
33 #include <vtksys/RegularExpression.hxx>
34 #include <vtksys/SystemTools.hxx>
35
36 #include <cassert>
37 #include <cctype>
38 #include <iterator>
39 #include <map>
40 #include <memory>
41 #include <sstream>
42 #include <string>
43 #include <vector>
44
45 namespace
46 {
47 // since certain compilers don't support std::to_string yet
48 template <typename T>
convert_to_string(const T & val)49 std::string convert_to_string(const T& val)
50 {
51 std::ostringstream str;
52 str << val;
53 return str.str();
54 }
55 }
56
57 //============================================================================
58 namespace parser
59 {
60 class Node
61 {
62 public:
63 Node() = default;
64 virtual ~Node() = default;
65 virtual bool Evaluate(vtkIdType offset) const = 0;
66 virtual void Print(ostream& os) const = 0;
67 };
68
69 class NodeVariable : public Node
70 {
71 vtkSignedCharArray* Data;
72 std::string Name;
73
74 public:
NodeVariable(vtkSignedCharArray * data,const std::string & name)75 NodeVariable(vtkSignedCharArray* data, const std::string& name)
76 : Data(data)
77 , Name(name)
78 {
79 }
Evaluate(vtkIdType offset) const80 bool Evaluate(vtkIdType offset) const override
81 {
82 assert(this->Data == nullptr || this->Data->GetNumberOfValues() > offset);
83 return this->Data ? (this->Data->GetValue(offset) != 0) : false;
84 }
Print(ostream & os) const85 void Print(ostream& os) const override { os << this->Name; }
86 };
87
88 class NodeNot : public Node
89 {
90 std::shared_ptr<Node> Child;
91
92 public:
NodeNot(const std::shared_ptr<Node> & node)93 NodeNot(const std::shared_ptr<Node>& node)
94 : Child(node)
95 {
96 }
Evaluate(vtkIdType offset) const97 bool Evaluate(vtkIdType offset) const override
98 {
99 assert(this->Child);
100 return !this->Child->Evaluate(offset);
101 }
Print(ostream & os) const102 void Print(ostream& os) const override
103 {
104 os << "!";
105 this->Child->Print(os);
106 }
107 };
108
109 class NodeAnd : public Node
110 {
111 std::shared_ptr<Node> ChildA;
112 std::shared_ptr<Node> ChildB;
113
114 public:
NodeAnd(const std::shared_ptr<Node> & nodeA,const std::shared_ptr<Node> & nodeB)115 NodeAnd(const std::shared_ptr<Node>& nodeA, const std::shared_ptr<Node>& nodeB)
116 : ChildA(nodeA)
117 , ChildB(nodeB)
118 {
119 }
Evaluate(vtkIdType offset) const120 bool Evaluate(vtkIdType offset) const override
121 {
122 assert(this->ChildA && this->ChildB);
123 return this->ChildA->Evaluate(offset) && this->ChildB->Evaluate(offset);
124 }
Print(ostream & os) const125 void Print(ostream& os) const override
126 {
127 os << "(";
128 this->ChildA->Print(os);
129 os << " & ";
130 this->ChildB->Print(os);
131 os << ")";
132 }
133 };
134
135 class NodeOr : public Node
136 {
137 std::shared_ptr<Node> ChildA;
138 std::shared_ptr<Node> ChildB;
139
140 public:
NodeOr(const std::shared_ptr<Node> & nodeA,const std::shared_ptr<Node> & nodeB)141 NodeOr(const std::shared_ptr<Node>& nodeA, const std::shared_ptr<Node>& nodeB)
142 : ChildA(nodeA)
143 , ChildB(nodeB)
144 {
145 }
Evaluate(vtkIdType offset) const146 bool Evaluate(vtkIdType offset) const override
147 {
148 assert(this->ChildA && this->ChildB);
149 return this->ChildA->Evaluate(offset) || this->ChildB->Evaluate(offset);
150 }
Print(ostream & os) const151 void Print(ostream& os) const override
152 {
153 os << "(";
154 this->ChildA->Print(os);
155 os << " | ";
156 this->ChildB->Print(os);
157 os << ")";
158 }
159 };
160 } // namespace parser
161
162 //============================================================================
163 class vtkSelection::vtkInternals
164 {
165 // applies the operator on the "top" (aka back) of the op_stack to the
166 // variables on the var_stack and pushes the result on the var_stack.
ApplyBack(std::vector<char> & op_stack,std::vector<std::shared_ptr<parser::Node>> & var_stack) const167 bool ApplyBack(
168 std::vector<char>& op_stack, std::vector<std::shared_ptr<parser::Node> >& var_stack) const
169 {
170 assert(op_stack.size() > 0);
171
172 if (op_stack.back() == '!')
173 {
174 if (var_stack.size() < 1)
175 {
176 // failed
177 return false;
178 }
179 const auto a = var_stack.back();
180 var_stack.pop_back();
181 var_stack.push_back(std::make_shared<parser::NodeNot>(a));
182 // pop the applied operator.
183 op_stack.pop_back();
184 return true;
185 }
186 else if (op_stack.back() == '|' || op_stack.back() == '&')
187 {
188 if (var_stack.size() < 2)
189 {
190 // failed!
191 return false;
192 }
193
194 const auto b = var_stack.back();
195 var_stack.pop_back();
196 const auto a = var_stack.back();
197 var_stack.pop_back();
198 if (op_stack.back() == '|')
199 {
200 var_stack.push_back(std::make_shared<parser::NodeOr>(a, b));
201 }
202 else
203 {
204 var_stack.push_back(std::make_shared<parser::NodeAnd>(a, b));
205 }
206 // pop the applied operator.
207 op_stack.pop_back();
208 return true;
209 }
210 return false;
211 }
212
213 // higher the value, higher the precedence.
precedence(char op) const214 inline int precedence(char op) const
215 {
216 switch (op)
217 {
218 case '|':
219 return -15;
220 case '&':
221 return -14;
222 case '!':
223 return -3;
224 case '(':
225 case ')':
226 return -1;
227 default:
228 return -100;
229 }
230 }
231
232 public:
233 std::map<std::string, vtkSmartPointer<vtkSelectionNode> > Items;
234 vtksys::RegularExpression RegExID;
235
vtkInternals()236 vtkInternals()
237 : RegExID("^[a-zA-Z0-9]+$")
238 {
239 }
240
BuildExpressionTree(const std::string & expression,const std::map<std::string,vtkSignedCharArray * > & values_map)241 std::shared_ptr<parser::Node> BuildExpressionTree(
242 const std::string& expression, const std::map<std::string, vtkSignedCharArray*>& values_map)
243 {
244 // We don't use PEGTL since it does not support all supported compilers viz.
245 // VS2013.
246 std::string accumated_text;
247 accumated_text.reserve(expression.size() + 64);
248
249 std::vector<std::string> parts;
250 for (auto ch : expression)
251 {
252 switch (ch)
253 {
254 case '(':
255 case ')':
256 case '|':
257 case '&':
258 case '!':
259 if (accumated_text.size())
260 {
261 parts.push_back(accumated_text);
262 accumated_text.clear();
263 }
264 parts.push_back(std::string(1, ch));
265 break;
266
267 default:
268 if (std::isalnum(ch))
269 {
270 accumated_text.push_back(ch);
271 }
272 break;
273 }
274 }
275 if (accumated_text.size())
276 {
277 parts.push_back(accumated_text);
278 }
279
280 std::vector<std::shared_ptr<parser::Node> > var_stack;
281 std::vector<char> op_stack;
282 for (const auto& term : parts)
283 {
284 if (term[0] == '(')
285 {
286 op_stack.push_back(term[0]);
287 }
288 else if (term[0] == ')')
289 {
290 // apply operators till we encounter the opening paren.
291 while (
292 op_stack.size() > 0 && op_stack.back() != '(' && this->ApplyBack(op_stack, var_stack))
293 {
294 }
295 if (op_stack.size() == 0)
296 {
297 // missing opening paren???
298 return nullptr;
299 }
300 assert(op_stack.back() == '(');
301 // pop the opening paren.
302 op_stack.pop_back();
303 }
304 else if (term[0] == '&' || term[0] == '|' || term[0] == '!')
305 {
306 while (op_stack.size() > 0 && (precedence(term[0]) < precedence(op_stack.back())) &&
307 this->ApplyBack(op_stack, var_stack))
308 {
309 }
310 // push the boolean operator on stack to eval later.
311 op_stack.push_back(term[0]);
312 }
313 else
314 {
315 auto iter = values_map.find(term);
316 auto dataptr = iter != values_map.end() ? iter->second : nullptr;
317 var_stack.push_back(std::make_shared<parser::NodeVariable>(dataptr, term));
318 }
319 }
320
321 while (!op_stack.empty() && this->ApplyBack(op_stack, var_stack))
322 {
323 }
324 return (op_stack.empty() && var_stack.size() == 1) ? var_stack.front() : nullptr;
325 }
326 };
327
328 //----------------------------------------------------------------------------
329 vtkStandardNewMacro(vtkSelection);
330
331 //----------------------------------------------------------------------------
vtkSelection()332 vtkSelection::vtkSelection()
333 : Expression()
334 , Internals(new vtkSelection::vtkInternals())
335 {
336 this->Information->Set(vtkDataObject::DATA_EXTENT_TYPE(), VTK_PIECES_EXTENT);
337 this->Information->Set(vtkDataObject::DATA_PIECE_NUMBER(), -1);
338 this->Information->Set(vtkDataObject::DATA_NUMBER_OF_PIECES(), 1);
339 this->Information->Set(vtkDataObject::DATA_NUMBER_OF_GHOST_LEVELS(), 0);
340 }
341
342 //----------------------------------------------------------------------------
~vtkSelection()343 vtkSelection::~vtkSelection()
344 {
345 delete this->Internals;
346 }
347
348 //----------------------------------------------------------------------------
Initialize()349 void vtkSelection::Initialize()
350 {
351 this->Superclass::Initialize();
352 this->RemoveAllNodes();
353 this->Expression.clear();
354 }
355
356 //----------------------------------------------------------------------------
GetNumberOfNodes() const357 unsigned int vtkSelection::GetNumberOfNodes() const
358 {
359 return static_cast<unsigned int>(this->Internals->Items.size());
360 }
361
362 //----------------------------------------------------------------------------
GetNode(unsigned int idx) const363 vtkSelectionNode* vtkSelection::GetNode(unsigned int idx) const
364 {
365 const vtkInternals& internals = (*this->Internals);
366 if (static_cast<unsigned int>(internals.Items.size()) > idx)
367 {
368 auto iter = std::next(internals.Items.begin(), static_cast<int>(idx));
369 assert(iter != internals.Items.end());
370 return iter->second;
371 }
372 return nullptr;
373 }
374
375 //----------------------------------------------------------------------------
GetNode(const std::string & name) const376 vtkSelectionNode* vtkSelection::GetNode(const std::string& name) const
377 {
378 const vtkInternals& internals = (*this->Internals);
379 auto iter = internals.Items.find(name);
380 if (iter != internals.Items.end())
381 {
382 return iter->second;
383 }
384 return nullptr;
385 }
386
387 //----------------------------------------------------------------------------
AddNode(vtkSelectionNode * node)388 std::string vtkSelection::AddNode(vtkSelectionNode* node)
389 {
390 if (!node)
391 {
392 return std::string();
393 }
394
395 const vtkInternals& internals = (*this->Internals);
396
397 // Make sure that node is not already added
398 for (const auto& pair : internals.Items)
399 {
400 if (pair.second == node)
401 {
402 return pair.first;
403 }
404 }
405
406 static vtkAtomicUInt64 counter = 0;
407 std::string name = std::string("node") + convert_to_string(++counter);
408 while (internals.Items.find(name) != internals.Items.end())
409 {
410 name = std::string("node") + convert_to_string(++counter);
411 }
412
413 this->SetNode(name, node);
414 return name;
415 }
416
417 //----------------------------------------------------------------------------
SetNode(const std::string & name,vtkSelectionNode * node)418 void vtkSelection::SetNode(const std::string& name, vtkSelectionNode* node)
419 {
420 vtkInternals& internals = (*this->Internals);
421 if (!node)
422 {
423 vtkErrorMacro("`node` cannot be null.");
424 }
425 else if (!internals.RegExID.find(name))
426 {
427 vtkErrorMacro("`" << name << "` is not in the expected form.");
428 }
429 else if (internals.Items[name] != node)
430 {
431 internals.Items[name] = node;
432 this->Modified();
433 }
434 }
435
436 //----------------------------------------------------------------------------
GetNodeNameAtIndex(unsigned int idx) const437 std::string vtkSelection::GetNodeNameAtIndex(unsigned int idx) const
438 {
439 const vtkInternals& internals = (*this->Internals);
440 if (static_cast<unsigned int>(internals.Items.size()) > idx)
441 {
442 auto iter = std::next(internals.Items.begin(), static_cast<int>(idx));
443 assert(iter != internals.Items.end());
444 return iter->first;
445 }
446 return std::string();
447 }
448
449 //----------------------------------------------------------------------------
RemoveNode(unsigned int idx)450 void vtkSelection::RemoveNode(unsigned int idx)
451 {
452 vtkInternals& internals = (*this->Internals);
453 if (static_cast<unsigned int>(internals.Items.size()) > idx)
454 {
455 auto iter = std::next(internals.Items.begin(), static_cast<int>(idx));
456 assert(iter != internals.Items.end());
457 internals.Items.erase(iter);
458 this->Modified();
459 }
460 }
461
462 //----------------------------------------------------------------------------
RemoveNode(const std::string & name)463 void vtkSelection::RemoveNode(const std::string& name)
464 {
465 vtkInternals& internals = (*this->Internals);
466 if (internals.Items.erase(name) == 1)
467 {
468 this->Modified();
469 }
470 }
471
472 //----------------------------------------------------------------------------
RemoveNode(vtkSelectionNode * node)473 void vtkSelection::RemoveNode(vtkSelectionNode* node)
474 {
475 vtkInternals& internals = (*this->Internals);
476 for (auto iter = internals.Items.begin(); iter != internals.Items.end(); ++iter)
477 {
478 if (iter->second == node)
479 {
480 internals.Items.erase(iter);
481 this->Modified();
482 break;
483 }
484 }
485 }
486
487 //----------------------------------------------------------------------------
RemoveAllNodes()488 void vtkSelection::RemoveAllNodes()
489 {
490 vtkInternals& internals = (*this->Internals);
491 if (internals.Items.size())
492 {
493 internals.Items.clear();
494 this->Modified();
495 }
496 }
497
498 //----------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)499 void vtkSelection::PrintSelf(ostream& os, vtkIndent indent)
500 {
501 this->Superclass::PrintSelf(os, indent);
502 unsigned int numNodes = this->GetNumberOfNodes();
503 os << indent << "Number of nodes: " << numNodes << endl;
504 os << indent << "Nodes: " << endl;
505 for (unsigned int i = 0; i < numNodes; i++)
506 {
507 os << indent << "Node #" << i << endl;
508 this->GetNode(i)->PrintSelf(os, indent.GetNextIndent());
509 }
510 }
511
512 //----------------------------------------------------------------------------
ShallowCopy(vtkDataObject * src)513 void vtkSelection::ShallowCopy(vtkDataObject* src)
514 {
515 if (auto* ssrc = vtkSelection::SafeDownCast(src))
516 {
517 this->Expression = ssrc->Expression;
518 this->Internals->Items = ssrc->Internals->Items;
519 this->Superclass::ShallowCopy(src);
520 this->Modified();
521 }
522 }
523
524 //----------------------------------------------------------------------------
DeepCopy(vtkDataObject * src)525 void vtkSelection::DeepCopy(vtkDataObject* src)
526 {
527 if (auto* ssrc = vtkSelection::SafeDownCast(src))
528 {
529 this->Expression = ssrc->Expression;
530
531 const auto& srcMap = ssrc->Internals->Items;
532 auto& destMap = this->Internals->Items;
533 destMap = srcMap;
534 for (auto& apair : destMap)
535 {
536 vtkNew<vtkSelectionNode> clone;
537 clone->DeepCopy(apair.second);
538 apair.second = clone;
539 }
540 this->Superclass::DeepCopy(src);
541 this->Modified();
542 }
543 }
544
545 //----------------------------------------------------------------------------
Union(vtkSelection * s)546 void vtkSelection::Union(vtkSelection* s)
547 {
548 for (unsigned int n = 0; n < s->GetNumberOfNodes(); ++n)
549 {
550 this->Union(s->GetNode(n));
551 }
552 }
553
554 //----------------------------------------------------------------------------
Union(vtkSelectionNode * node)555 void vtkSelection::Union(vtkSelectionNode* node)
556 {
557 bool merged = false;
558 for (unsigned int tn = 0; tn < this->GetNumberOfNodes(); ++tn)
559 {
560 vtkSelectionNode* tnode = this->GetNode(tn);
561 if (tnode->EqualProperties(node))
562 {
563 tnode->UnionSelectionList(node);
564 merged = true;
565 break;
566 }
567 }
568 if (!merged)
569 {
570 vtkSmartPointer<vtkSelectionNode> clone =
571 vtkSmartPointer<vtkSelectionNode>::New();
572 clone->DeepCopy(node);
573 this->AddNode(clone);
574 }
575 }
576
577 //----------------------------------------------------------------------------
Subtract(vtkSelection * s)578 void vtkSelection::Subtract(vtkSelection* s)
579 {
580 for(unsigned int n=0; n<s->GetNumberOfNodes(); ++n)
581 {
582 this->Subtract(s->GetNode(n));
583 }
584 }
585
586 //----------------------------------------------------------------------------
Subtract(vtkSelectionNode * node)587 void vtkSelection::Subtract(vtkSelectionNode* node)
588 {
589 bool subtracted = false;
590 for( unsigned int tn = 0; tn<this->GetNumberOfNodes(); ++tn)
591 {
592 vtkSelectionNode* tnode = this->GetNode(tn);
593
594 if(tnode->EqualProperties(node))
595 {
596 tnode->SubtractSelectionList(node);
597 subtracted = true;
598 }
599 }
600 if( !subtracted )
601 {
602 vtkErrorMacro("Could not subtract selections");
603 }
604 }
605
606 //----------------------------------------------------------------------------
GetMTime()607 vtkMTimeType vtkSelection::GetMTime()
608 {
609 vtkMTimeType mtime = this->Superclass::GetMTime();
610 const vtkInternals& internals = (*this->Internals);
611 for (const auto& apair : internals.Items)
612 {
613 mtime = std::max(mtime, apair.second->GetMTime());
614 }
615 return mtime;
616 }
617
618 //----------------------------------------------------------------------------
GetData(vtkInformation * info)619 vtkSelection* vtkSelection::GetData(vtkInformation* info)
620 {
621 return info? vtkSelection::SafeDownCast(info->Get(DATA_OBJECT())) : nullptr;
622 }
623
624 //----------------------------------------------------------------------------
GetData(vtkInformationVector * v,int i)625 vtkSelection* vtkSelection::GetData(vtkInformationVector* v, int i)
626 {
627 return vtkSelection::GetData(v->GetInformationObject(i));
628 }
629
630 //----------------------------------------------------------------------------
Evaluate(vtkSignedCharArray * const * values,unsigned int num_values) const631 vtkSmartPointer<vtkSignedCharArray> vtkSelection::Evaluate(
632 vtkSignedCharArray* const* values, unsigned int num_values) const
633 {
634 std::map<std::string, vtkSignedCharArray*> values_map;
635
636 vtkIdType numVals = -1;
637 unsigned int cc = 0;
638 const vtkInternals& internals = (*this->Internals);
639 for (const auto& apair : internals.Items)
640 {
641 vtkSignedCharArray* array = cc < num_values ? values[cc] : nullptr;
642 if (array == nullptr)
643 {
644 // lets assume null means false.
645 }
646 else
647 {
648 if (array->GetNumberOfComponents() != 1)
649 {
650 vtkGenericWarningMacro("Only single-component arrays are supported!");
651 return nullptr;
652 }
653 if (numVals != -1 && array->GetNumberOfTuples() != numVals)
654 {
655 vtkGenericWarningMacro("Mismatched number of tuples.");
656 return nullptr;
657 }
658 numVals = array->GetNumberOfTuples();
659 }
660 values_map[apair.first] = array;
661 cc++;
662 }
663
664 std::string expr = this->Expression;
665 if (expr.empty())
666 {
667 bool add_separator = false;
668 std::ostringstream stream;
669 for (const auto& apair : internals.Items)
670 {
671 stream << (add_separator ? "|" : "") << apair.first;
672 add_separator = true;
673 }
674 expr = stream.str();
675 }
676
677 auto tree = this->Internals->BuildExpressionTree(expr, values_map);
678 if (tree && (values_map.size() > 0))
679 {
680 auto result = vtkSmartPointer<vtkSignedCharArray>::New();
681 result->SetNumberOfComponents(1);
682 result->SetNumberOfTuples(numVals);
683 vtkSMPTools::For(0, numVals, [&](vtkIdType start, vtkIdType end) {
684 for (vtkIdType idx = start; idx < end; ++idx)
685 {
686 result->SetTypedComponent(idx, 0, tree->Evaluate(idx));
687 }
688 });
689 return result;
690 }
691 else if (!tree)
692 {
693 vtkGenericWarningMacro("Failed to parse expression: " << this->Expression);
694 }
695 return nullptr;
696 }
697
698 //----------------------------------------------------------------------------
Dump()699 void vtkSelection::Dump()
700 {
701 this->Dump(cout);
702 }
703
704 //----------------------------------------------------------------------------
Dump(ostream & os)705 void vtkSelection::Dump(ostream& os)
706 {
707 vtkSmartPointer<vtkTable> tmpTable = vtkSmartPointer<vtkTable>::New();
708 cerr << "==Selection==" << endl;
709 for (unsigned int i = 0; i < this->GetNumberOfNodes(); ++i)
710 {
711 os << "===Node " << i << "===" << endl;
712 vtkSelectionNode* node = this->GetNode(i);
713 os << "ContentType: ";
714 switch (node->GetContentType())
715 {
716 case vtkSelectionNode::GLOBALIDS:
717 os << "GLOBALIDS";
718 break;
719 case vtkSelectionNode::PEDIGREEIDS:
720 os << "PEDIGREEIDS";
721 break;
722 case vtkSelectionNode::VALUES:
723 os << "VALUES";
724 break;
725 case vtkSelectionNode::INDICES:
726 os << "INDICES";
727 break;
728 case vtkSelectionNode::FRUSTUM:
729 os << "FRUSTUM";
730 break;
731 case vtkSelectionNode::LOCATIONS:
732 os << "LOCATIONS";
733 break;
734 case vtkSelectionNode::THRESHOLDS:
735 os << "THRESHOLDS";
736 break;
737 case vtkSelectionNode::BLOCKS:
738 os << "BLOCKS";
739 break;
740 case vtkSelectionNode::USER:
741 os << "USER";
742 break;
743 default:
744 os << "UNKNOWN";
745 break;
746 }
747 os << endl;
748 os << "FieldType: ";
749 switch (node->GetFieldType())
750 {
751 case vtkSelectionNode::CELL:
752 os << "CELL";
753 break;
754 case vtkSelectionNode::POINT:
755 os << "POINT";
756 break;
757 case vtkSelectionNode::FIELD:
758 os << "FIELD";
759 break;
760 case vtkSelectionNode::VERTEX:
761 os << "VERTEX";
762 break;
763 case vtkSelectionNode::EDGE:
764 os << "EDGE";
765 break;
766 case vtkSelectionNode::ROW:
767 os << "ROW";
768 break;
769 default:
770 os << "UNKNOWN";
771 break;
772 }
773 os << endl;
774 if (node->GetSelectionData())
775 {
776 tmpTable->SetRowData(node->GetSelectionData());
777 tmpTable->Dump(10);
778 }
779 }
780 }
781