1 /* -------------------------------------------------------------------------- *
2 * OpenSim: XMLDocument.cpp *
3 * -------------------------------------------------------------------------- *
4 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. *
5 * See http://opensim.stanford.edu and the NOTICE file for more information. *
6 * OpenSim is developed at Stanford University and supported by the US *
7 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA *
8 * through the Warrior Web program. *
9 * *
10 * Copyright (c) 2005-2017 Stanford University and the Authors *
11 * Author(s): Frank C. Anderson *
12 * *
13 * Licensed under the Apache License, Version 2.0 (the "License"); you may *
14 * not use this file except in compliance with the License. You may obtain a *
15 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
16 * *
17 * Unless required by applicable law or agreed to in writing, software *
18 * distributed under the License is distributed on an "AS IS" BASIS, *
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
20 * See the License for the specific language governing permissions and *
21 * limitations under the License. *
22 * -------------------------------------------------------------------------- */
23
24 /* Note: This code was originally developed by Realistic Dynamics Inc.
25 * Author: Frank C. Anderson
26 */
27
28
29 //-----------------------------------------------------------------------------
30 // INCLUDES
31 //-----------------------------------------------------------------------------
32 #include "XMLDocument.h"
33 #include "Object.h"
34 #include <functional>
35
36
37 using namespace OpenSim;
38 using namespace std;
39
40
41 //-----------------------------------------------------------------------------
42 // CONSTANTS
43 //-----------------------------------------------------------------------------
44
45 // This list of version numbers is not complete
46 // 20301 for separation of RRATool, CMCTool
47 // 20302 for Muscle's pennation_angle -> pennation_angle_at_optimal
48 // 20303
49 // 30000 for OpenSim 3.0 release
50 // 30500 for OpenSim 4.0 development and Connectors
51 // 30501 for changing serialization of Marker
52 // 30502 for changing serialization of Geometry
53 // 30503 for changing serialization of Ground
54 // 30505 for changing serialization of Joint to create offset frames
55 // 30506 for testing 30505 conversion code
56 // 30507 for changing serialization of Coordinates owned by Joint
57 // 30508 for moving Connector's connectee_name to enclosing Component.
58 // 30509 for replacing 'isDisabled' with: 'appliesForce', 'isEnforced' and
59 // 'enabled', for Force, Constraint and Controller, respectively
60 // 30510 for renaming Connector to Socket.
61 // 30511 for replacing Probe::isDisabled with Probe::enabled.
62 // 30512 for removing Model::FrameSet and moving frames to components list
63 // 30513 for removing internal (silent) clamping of Muscle controls (excitations)
64 // 30514 for removing "reverse" property from Joint
65 // 30515 for WrapObject color, display_preference, VisibleObject -> Appearance
66 // 30516 for GeometryPath default_color -> Appearance
67 // 30517 for removal of _connectee_name suffix to shorten XML for socket, input
68 // 40000 for OpenSim 4.0 release
69 const int XMLDocument::LatestVersion = 40000;
70 //=============================================================================
71 // DESTRUCTOR AND CONSTRUCTOR(S)
72 //=============================================================================
73 //-----------------------------------------------------------------------------
74 // DESTRUCTOR
75 //-----------------------------------------------------------------------------
76 //_____________________________________________________________________________
77 /**
78 * Handle delete of an XMLDocument object.
79 */
~XMLDocument()80 XMLDocument::~XMLDocument()
81 {
82 for(int i = 0; i < _defaultObjects.size(); i++) {
83 delete _defaultObjects.get(i);
84 }
85 _defaultObjects.setSize(0);
86 }
87
88 //-----------------------------------------------------------------------------
89 // CONSTRUCTOR(S)
90 //-----------------------------------------------------------------------------
91 //_____________________________________________________________________________
92 /**
93 * Construct a XMLDocument object with a locally generated DOMDocument.
94 * This constructor is used when an XML document is going to be generated
95 * locally in memory without reference to an XML file. The initial
96 * DOMDocument is empty.
97 */
XMLDocument()98 XMLDocument::XMLDocument()
99 {
100 setRootTag("OpenSimDocument");
101 stringstream latestVersionString;
102 latestVersionString << LatestVersion;
103 _documentVersion = LatestVersion;
104 getRootElement().setAttributeValue("Version", latestVersionString.str());
105 }
106
107 //_____________________________________________________________________________
108 /**
109 * Construct an XMLDocument object from an XML document.
110 * A parser is created for the purpose of reading in the XML file.
111 *
112 * @param aFileName File name of the XML document.
113 */
XMLDocument(const string & aFileName)114 XMLDocument::XMLDocument(const string &aFileName) :
115 SimTK::Xml::Document(aFileName)
116 {
117
118
119 _fileName = aFileName;
120
121 // Update document version based on parsing
122 updateDocumentVersion();
123 }
124
125 //_____________________________________________________________________________
126 /**
127 * Construct a copy of an XMLDocument object. The document an all its nodes
128 * are copied; however, the parser associated with the copied document, if
129 * any, is not copied.
130 */
XMLDocument(const XMLDocument & aDocument)131 XMLDocument::XMLDocument(const XMLDocument &aDocument):
132 SimTK::Xml::Document(aDocument)
133 {
134 _documentVersion = aDocument.getDocumentVersion();
135 _fileName = aDocument.getFileName();
136 }
137
138
139
140 //=============================================================================
141 // CONSTRUCTION
142 //=============================================================================
143 //_____________________________________________________________________________
144
145
146 //=============================================================================
147 // SET AND GET
148 //=============================================================================
149 //-----------------------------------------------------------------------------
150 // DOCUMENT
151 //-----------------------------------------------------------------------------
152
153 void XMLDocument::
setFileName(const string & aFileName)154 setFileName(const string &aFileName)
155 {
156 _fileName = aFileName;
157 }
158
159 const string &XMLDocument::
getFileName() const160 getFileName() const
161 {
162 return _fileName;
163 }
164
165 //=============================================================================
166 // IO
167 //=============================================================================
168 //-----------------------------------------------------------------------------
169 // PRINT
170 //-----------------------------------------------------------------------------
171 //_____________________________________________________________________________
172 /**
173 * Print the XML document to file.
174 *
175 * @param aFileName File name of the document to which to print
176 */
177 bool XMLDocument::
print(const string & aFileName)178 print(const string &aFileName)
179 {
180 // Standard Out
181 if(aFileName.empty()) {
182 cout << *this;
183 cout << flush;
184 // File
185 } else {
186 setIndentString("\t");
187 writeToFile(aFileName);
188 }
189 return true;
190 }
191 //_____________________________________________________________________________
192
193 //-----------------------------------------------------------------------------
194 // FORMATTER
195 //-----------------------------------------------------------------------------
196 //-----------------------------------------------------------------------------
197 // STREAM OUTPUT
198 //-----------------------------------------------------------------------------
199 //_____________________________________________________________________________
200
201 //--------------------------------------------------------------------------
202 // VERSIONING /BACKWARD COMPATIBILITY SUPPORT
203 //--------------------------------------------------------------------------
204 //_____________________________________________________________________________
205 /**
206 * Convert passed in version number to a string
207 * The string is usually more compact e.g. 010100 -> 1_1 (rather than 11 which would confuse 110000 with 010100)
208 */
209 void XMLDocument::
getVersionAsString(const int aVersion,std::string & aString)210 getVersionAsString(const int aVersion, std::string& aString)
211 {
212 char pad[3];
213 int ver = aVersion;
214 aString = "";
215 int div = 10000;
216 for(int i=0; i<3; i++)
217 {
218 int digits = ver / div;
219 sprintf(pad, "%02d",digits);
220 ver -= div*(ver / div);
221 div /=100;
222 aString += string(pad);
223 if (ver ==0) break;
224 aString +=(i<2?"_":"");
225 }
226 }
227 //_____________________________________________________________________________
228 /**
229 * Update member variable _documentVersion based on parsing
230 * Key assumption is that parsing has finished but no Object parsing is done yet
231 */
232 void XMLDocument::
updateDocumentVersion()233 updateDocumentVersion()
234 {
235 // Check root node if it's OpenSimDocument
236 std::string rootTag = getRootTag();
237 if (rootTag == "OpenSimDocument"){
238 _documentVersion = getRootElement().getRequiredAttributeValueAs<int>("Version");
239 }
240 else {
241 _documentVersion = 10500; // Old version pre 1.6
242 }
243
244 // Validate >= 10500 and < latest as sanity check
245 assert(_documentVersion >= 10500 && _documentVersion <= LatestVersion);
246 }
247 //_____________________________________________________________________________
248 /**
249 * getRootDataElement returns a pointer to the real root node that contains objects
250 * works as a wrapper to get around the new root node <OpenSimDocument introduced in 1.6
251 */
252 SimTK::Xml::Element XMLDocument::
getRootDataElement()253 getRootDataElement()
254 {
255 // Check root node if it's OpenSimDocument
256 std::string rootTag = getRootTag();
257 if (rootTag == "OpenSimDocument"){
258 _documentVersion = getRootElement().getRequiredAttributeValueAs<int>("Version");
259 return (*getRootElement().element_begin());
260 }
261 else {
262 _documentVersion = 10500; // Old version pre 1.6
263 return getRootElement();
264 }
265 }
266 /**
267 */
addDefaultObject(OpenSim::Object * aDefaultObject)268 void XMLDocument::addDefaultObject(OpenSim::Object *aDefaultObject)
269 {
270 _defaultObjects.append(aDefaultObject);
271 }
writeDefaultObjects(SimTK::Xml::Element & elmt)272 void XMLDocument::writeDefaultObjects(SimTK::Xml::Element& elmt)
273 {
274 if (_defaultObjects.getSize()==0) return;
275 // Make node for "defaults"
276 SimTK::Xml::Element defaultsElement("defaults");
277
278 elmt.insertNodeAfter(elmt.node_end(), defaultsElement);
279 for(int i=0; i < _defaultObjects.getSize(); i++){
280 _defaultObjects.get(i)->updateXMLNode(defaultsElement);
281 }
282 }
283
copyDefaultObjects(const XMLDocument & aDocument)284 void XMLDocument::copyDefaultObjects(const XMLDocument &aDocument){
285 _defaultObjects.setSize(0);
286 for (int i=0; i< aDocument._defaultObjects.getSize(); i++)
287 _defaultObjects.append(aDocument._defaultObjects.get(i)->clone());
288 }
289
290 /*static*/
renameChildNode(SimTK::Xml::Element & aNode,std::string oldElementName,std::string newElementName)291 void XMLDocument::renameChildNode(SimTK::Xml::Element& aNode, std::string oldElementName, std::string newElementName)
292 {
293 SimTK::Xml::element_iterator elmtIter(aNode.element_begin(oldElementName));
294 if (elmtIter!=aNode.element_end()){
295 elmtIter->setElementTag(newElementName);
296 }
297 }
298
isEqualTo(XMLDocument & aOtherDocument,double toleranceForDoubles,bool compareDefaults,bool compareVersionNumbers)299 bool XMLDocument::isEqualTo(XMLDocument& aOtherDocument, double toleranceForDoubles, bool compareDefaults, bool compareVersionNumbers)
300 {
301 bool equal = true;
302
303 if (compareVersionNumbers)
304 equal = (_documentVersion == aOtherDocument._documentVersion);
305 if (!equal) return false;
306 // Get Roots
307 SimTK::Xml::Element root1= getRootElement();
308 SimTK::Xml::Element root2= aOtherDocument.getRootElement();
309
310 //if (!equal) return false;
311 // Cycle through children and compare. Order is assumed to be the same for now
312 SimTK::Array_<SimTK::Xml::Element> elts1 = root1.getAllElements();
313 SimTK::Array_<SimTK::Xml::Element> elts2 = root2.getAllElements();
314 if (elts1.size() != elts2.size()){
315 cout << "Different number of children at Top level" << endl;
316 equal = false;
317 }
318 if (!equal) return false;
319 // Recursively compare Elements
320 SimTK::String s1,s2;
321 for(unsigned it = 0; it < elts1.size(); it++){
322 elts1[it].writeToString(s1);
323 elts2[it].writeToString(s2);
324
325 if (elts1[it].getElementTag()==elts2[it].getElementTag() && elts1[it].getElementTag()=="defaults" && !compareDefaults)
326 continue;
327 equal = isElementEqual(elts1[it], elts2[it], toleranceForDoubles);
328 if (!equal){
329 cout << elts1[it].getElementTag() << " is different" << endl;
330 return false;
331 }
332 }
333 return true;
334 }
335
isElementEqual(SimTK::Xml::Element & elt1,SimTK::Xml::Element & elt2,double toleranceForDoubles)336 bool XMLDocument::isElementEqual(SimTK::Xml::Element& elt1, SimTK::Xml::Element& elt2, double toleranceForDoubles)
337 {
338 SimTK::String s1,s2;
339 elt1.writeToString(s1);
340 elt2.writeToString(s2);
341 SimTK::Xml::attribute_iterator att1 = elt1.attribute_begin();
342 SimTK::Xml::attribute_iterator att2 = elt2.attribute_begin();
343 // Handle different # attributes
344 if ( (att1 == elt1.attribute_end() && att2 != elt2.attribute_end()) ||
345 (att1 != elt1.attribute_end() && att2 == elt2.attribute_end()) ){
346 cout << "Number of attributes is different, element " << elt1.getElementTag() << endl;
347 return false;
348 }
349 bool equal =true;
350 // Same size attributes including none
351 for(att1 = elt1.attribute_begin(); att1 != elt1.attribute_end() && equal; att1++, att2++){
352 equal = (att1->getName() == att2->getName());
353 equal = equal && (att1->getValue() == att2->getValue());
354 if (!equal) {
355 cout << "Attribute " << att1->getName() << " is different " << att1->getValue() <<
356 "vs." << att2->getValue() << endl;
357 }
358 }
359 if (!equal) return false;
360
361 // Attributes match now children
362 SimTK::Array_<SimTK::Xml::Element> elts1 = elt1.getAllElements();
363 SimTK::Array_<SimTK::Xml::Element> elts2 = elt2.getAllElements();
364 if (elts1.size() != elts2.size()){
365 cout << "Different number of children for Element " << elt1.getElementTag() << endl;
366 equal = false;
367 }
368 if (!equal) return false;
369 // Recursively compare Elements unless Value Elements in that case do direct compare
370 for(unsigned it = 0; it < elts1.size() && equal; it++){
371 SimTK::String elt1Tag = elts1[it].getElementTag();
372 cout << "Compare " << elt1Tag << endl;
373 SimTK::Xml::element_iterator elt2_iter = elt2.element_begin(elt1Tag);
374 if (elt2_iter==elt2.element_end()){
375 cout << "Element " << elt1Tag << " was not found in reference document" << endl;
376 equal = false;
377 break;
378 }
379 bool value1 = elts1[it].isValueElement();
380 bool value2 = elt2_iter->isValueElement();
381 equal = (value1 == value2);
382 if (!equal){
383 cout << elts1[it].getElementTag() << " is different. One is Value Element the other isn't" << endl;
384 return false;
385 }
386 if (value1){
387 // We should check if this's a double or array of doubles in that case we can getValueAs<double>
388 try {
389 SimTK::Array_<double> v1, v2;
390 elts1[it].getValueAs(v1);
391 elt2_iter->getValueAs(v2);
392 for(unsigned ix=0; ix<v1.size() && equal; ix++)
393 equal = (std::fabs(v1[ix]-v2[ix]) < toleranceForDoubles);
394 }
395 catch(...){
396 equal = (elts1[it].getValue() == elt2_iter->getValue());
397 }
398 }
399 else // recur
400 equal = isElementEqual(elts1[it], elts2[it], toleranceForDoubles);
401 if (!equal){
402 cout << elts1[it].getElementTag() << " is different" << endl;
403 SimTK::String pad;
404 elts1[it].writeToString(pad);
405 cout << pad << endl;
406 cout << "------------------- vs. ------" << endl;
407 elts2[it].writeToString(pad);
408 cout << pad << endl;
409 return equal;
410 }
411 }
412
413 return equal;
414 }
415
416 /*
417 * Helper function to add connector to the xmlElement passed in
418 */
addConnector(SimTK::Xml::Element & element,const std::string & connectorTag,const std::string & connectorName,const std::string & connectorValue)419 void XMLDocument::addConnector(SimTK::Xml::Element& element,
420 const std::string& connectorTag, const std::string& connectorName,
421 const std::string& connectorValue)
422 {
423 SimTK::Xml::element_iterator connectors_node = element.element_begin("connectors");
424 //SimTK::String debug; //Only used for debugging
425 if (connectors_node == element.element_end()){
426 SimTK::Xml::Element connectorsElement("connectors");
427 element.insertNodeBefore(element.element_begin(), connectorsElement);
428 connectors_node = element.element_begin("connectors");
429 }
430 // Here we're guaranteed connectors node exists, add individual connector
431 SimTK::Xml::Element newConnectorElement(connectorTag);
432 newConnectorElement.setAttributeValue("name", connectorName);
433 //newConnectorElement.writeToString(debug);
434
435 SimTK::Xml::Element connecteeElement("connectee_name");
436 connecteeElement.insertNodeAfter(connecteeElement.element_end(), SimTK::Xml::Text(connectorValue));
437 // Insert text under newConnectorElement
438 newConnectorElement.insertNodeAfter(newConnectorElement.element_end(), connecteeElement);
439 connectors_node->insertNodeAfter(connectors_node->element_end(), newConnectorElement);
440 //connectors_node->writeToString(debug);
441 }
442
updateConnectors30508(SimTK::Xml::Element & componentElt)443 void XMLDocument::updateConnectors30508(SimTK::Xml::Element& componentElt)
444 {
445 using ElementItr = SimTK::Xml::element_iterator;
446
447 ElementItr connectors_node = componentElt.element_begin("connectors");
448
449 // See if there's a <connectors> element.
450 if (connectors_node == componentElt.element_end()) return;
451
452 for (ElementItr connectorElt = connectors_node->element_begin();
453 connectorElt != componentElt.element_end();
454 ++connectorElt) {
455 // Grab name of Connector.
456 const auto& connectorName =
457 connectorElt->getRequiredAttributeValue("name");
458 // Grab value of connectee_name property.
459 ElementItr connecteeNameElt =
460 connectorElt->element_begin("connectee_name");
461 SimTK::String connecteeName;
462 connecteeNameElt->getValueAs<std::string>(connecteeName);
463
464 // Create new element for this connector's connectee name.
465 SimTK::Xml::Element newConnecteeNameElt(
466 "connector_" + connectorName + "_connectee_name");
467 newConnecteeNameElt.setValue(connecteeName);
468 componentElt.insertNodeAfter(connectors_node, newConnecteeNameElt);
469 }
470
471 // No longer want the old syntax for connectors.
472 componentElt.eraseNode(connectors_node);
473 }
474
addPhysicalOffsetFrame30505_30517(SimTK::Xml::Element & element,const std::string & frameName,const std::string & parentFrameName,const SimTK::Vec3 & location,const SimTK::Vec3 & orientation)475 void XMLDocument::addPhysicalOffsetFrame30505_30517(SimTK::Xml::Element& element,
476 const std::string& frameName,
477 const std::string& parentFrameName,
478 const SimTK::Vec3& location, const SimTK::Vec3& orientation)
479 {
480 SimTK::Xml::element_iterator frames_node = element.element_begin("frames");
481 //SimTK::String debug; //Only used for debugging
482
483 if (frames_node == element.element_end()) {
484 SimTK::Xml::Element framesElement("frames");
485 element.insertNodeBefore(element.element_begin(), framesElement);
486 frames_node = element.element_begin("frames");
487 }
488 // Here we're guaranteed frames node exists, add individual frame
489 SimTK::Xml::Element newFrameElement("PhysicalOffsetFrame");
490 newFrameElement.setAttributeValue("name", frameName);
491 //newFrameElement.writeToString(debug);
492
493 // This function always adds the frame as a subcomponent of a component
494 // that is a member of the forceset, making this frame three levels
495 // deep. The connectee is either ground or a member of bodyset.
496 if(parentFrameName == "ground")
497 XMLDocument::addConnector(newFrameElement,
498 "Connector_PhysicalFrame_", "parent", "../../../"
499 + parentFrameName);
500 else
501 XMLDocument::addConnector(newFrameElement,
502 "Connector_PhysicalFrame_", "parent", "../../../bodyset/"
503 + parentFrameName);
504
505 std::ostringstream transValue;
506 transValue << location[0] << " " << location[1] << " " << location[2];
507 SimTK::Xml::Element translationElement("translation", transValue.str());
508 newFrameElement.insertNodeAfter(newFrameElement.element_end(), translationElement);
509
510 std::ostringstream orientValue;
511 orientValue << orientation[0] << " " << orientation[1] << " " << orientation[2];
512 SimTK::Xml::Element orientationElement("orientation", orientValue.str());
513 newFrameElement.insertNodeAfter(newFrameElement.element_end(), orientationElement);
514
515 frames_node->insertNodeAfter(frames_node->element_end(), newFrameElement);
516 //frames_node->writeToString(debug);
517 }
518
updateConnecteePath30517(const std::string & connecteeSetName,const std::string & connecteeName)519 string XMLDocument::updateConnecteePath30517(
520 const std::string& connecteeSetName,
521 const std::string& connecteeName) {
522 std::string connecteePath;
523 if (connecteeSetName == "bodyset" && connecteeName == "ground") {
524 connecteePath = "/" + connecteeName;
525 } else{
526 connecteePath = "/" + connecteeSetName + "/" + connecteeName;
527 }
528 return connecteePath;
529 }
530
findElementWithName(SimTK::Xml::Element & element,const std::string & name)531 SimTK::Xml::Element XMLDocument::findElementWithName(
532 SimTK::Xml::Element& element, const std::string& name) {
533 using namespace SimTK;
534
535 if (name.empty()) return Xml::Element();
536
537 // First, get to the root of the XML document.
538 Xml::Element current = element;
539 while (current.hasParentElement())
540 current = current.getParentElement();
541 Xml::Element root = current;
542
543 // This will be a recursive lambda function.
544 std::function<Xml::Element(Xml::Element&, const std::string&)>
545 searchForElement;
546 // For recursion, must capture the function itself.
547 // Returns an invalid Element if no element with `name` could be found.
548 searchForElement = [&searchForElement](
549 Xml::Element& elem, const std::string& name) -> Xml::Element {
550 // This is a depth-first search.
551 for (auto it = elem.element_begin(); it != elem.element_end();
552 ++it) {
553 std::string elemName = it->getOptionalAttributeValue("name");
554 if (elemName == name)
555 return elem;
556 Xml::Element foundElem = searchForElement(*it, name);
557 if (foundElem.isValid())
558 return foundElem;
559 // Keep searching other branches.
560 }
561 return Xml::Element(); // Did not find.
562 };
563 return searchForElement(root, name);
564 }
565