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