Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:34

0001 
0002 // Copyright 2008-2025, Jefferson Science Associates, LLC.
0003 // Subject to the terms in the LICENSE file found in the top-level directory.
0004 // Author: David Lawrence
0005 
0006 #include <unistd.h>
0007 
0008 #include <iostream>
0009 #include <fstream>
0010 #include <string>
0011 
0012 using namespace std;
0013 
0014 #include <JANA/JApplication.h>
0015 #include <JANA/Calibrations/JCalibrationManager.h>
0016 #include <JANA/Geometry/JGeometryXML.h>
0017 #include <JANA/Services/JParameterManager.h>
0018 #include <md5.h>
0019 
0020 #if JANA2_HAVE_XERCES
0021 using namespace xercesc;
0022 #endif
0023 
0024 
0025 //---------------------------------
0026 // JGeometryXML    (Constructor)
0027 //---------------------------------
0028 JGeometryXML::JGeometryXML(string url, int run, string context):JGeometry(url,run,context)
0029 {
0030     /// File URL should be of form:
0031     ///
0032     /// xmlfile:///path-to-xmlfile
0033     ///
0034     ///   or
0035     ///
0036     /// ccdb://path-to-dir-in-ccdb
0037     ///
0038     /// If multiple XML files are required, then this should point to a
0039     /// top level file that includes all of the others. If using CCDB,
0040     /// the given directory is search for items corresponding to the
0041     /// specified run and context. All items found are assumed to be
0042     /// XML files and are read in.
0043 
0044     // Initialize our flag until we confirm the URL points to valid XML
0045     valid_xmlfile = false;
0046     md5_checksum = "";
0047     jcalib = NULL;
0048 #if JANA2_HAVE_XERCES
0049     parser = NULL;
0050     doc = NULL;
0051 #endif
0052 
0053     string xmlfile;
0054     string xml;
0055 
0056     if(url.find("xmlfile://")==0){
0057         //-------------- FILE -------------------
0058         // Fill basedir with path to directory.
0059         xmlfile = url.substr(10); // wipe out "xmlfile://" part
0060 
0061         // Try and open top-level XML file to see if it exists and is readable
0062         if( access(xmlfile.c_str(), R_OK)){
0063             jerr << " Unable to open \""<<xmlfile<<"\"! Geometry info not available!!"<<endl;
0064             run_min = run_max = -1;
0065             return;
0066         }
0067 
0068     }else if(url.find("ccdb://")==0){
0069         //-------------- CCDB -------------------
0070         // Get calibration object
0071         xmlfile = url.substr(7); // wipe out "ccdb://" part
0072         jcalib = japp->GetService<JCalibrationManager>()->GetJCalibration((uint32_t)run);
0073 
0074         // Read in top level file from Calib DB
0075         vector< map<string, string> > vals;
0076         jcalib->GetCalib(xmlfile, vals);
0077         if(vals.empty()){
0078             jerr << " Failed to get XML for " << xmlfile << "! Geometry info not available!!" << endl;
0079             run_min = run_max = -1;
0080             return;
0081         }
0082         xml = vals[0].begin()->second;
0083 
0084 //      vector<string> allnamepaths;
0085 //      jcalib->GetListOfNamepaths(allnamepaths);
0086 //      map<string, string> xml_files; // key="file" name  val=xml
0087 //      for(auto s : allnamepaths){
0088 //          if( (s.find("GEOMETRY/")==0) && (s.find(".xml")!=string::npos) ) {
0089 //
0090 //              vector< map<string, string> > vals;
0091 //              jcalib->GetCalib(s, vals);
0092 //              if(!vals.empty()){
0093 //                  string &x = vals[0].begin()->second;
0094 //                  xml_files[s] = x;
0095 //              }else{
0096 //                  jerr << "Failed to get XML for " << s <<  endl;
0097 //              }
0098 //          }
0099 //      }
0100 //      
0101 //      // Loop over all XML entities, replacing any includes with the entire
0102 //      // entity. This is needed since we can't use the normal file mechanism.
0103 //      for(auto p : xml_files){
0104 //          map<string, string> entities; // key=entity name  val=XML filename (calib itemname)
0105 //          stringstream ss(p.second);
0106 //          string line;
0107 //          while( getline(ss, line) ){
0108 //              // Check if this line defines an ENTITY
0109 //              auto pos = line.find("<!ENTITY");
0110 //              if(pos != string::npos){
0111 //                  stringstream sss(ss.str().substr(pos+1));
0112 //                  vector<string> tokens;
0113 //                  
0114 //              }
0115 //          }
0116 //      }
0117 //      
0118 
0119     }else{
0120         _DBG_<<"Poorly formed URL. Should start with \"xmlfile://\" or \"ccdb://\"."<<endl;
0121         _DBG_<<"URL:"<<url<<endl;
0122         _DBG_<<"(Try setting you JANA_GEOMETRY_URL environment variable.)"<<endl;
0123         return;
0124     }
0125 
0126 
0127     // Initialize class with XML obtained above
0128     Init(xmlfile, xml);
0129 
0130     // Among other things, this should contain the range of runs for which
0131     // this calibration is valid. For now, just set them all to run_requested.
0132     run_min = run_max = run_found = GetRunRequested();
0133 
0134 }
0135 
0136 //---------------------------------
0137 // Init
0138 //---------------------------------
0139 void JGeometryXML::Init(string xmlfile, string xml)
0140 {
0141     /// This will do the actual work of parsing the XML.  It is called from
0142     /// the constructor. Upon entry, xmlfile will contain the path to the
0143     /// top-level file (with any protocol prefix stripped away). This may be
0144     /// a file in the local filesystem or a path in the Calib DB. If we are
0145     /// reading from the Calib DB, then xml will be a non-empty string with
0146     /// the contents of the top-level file already read in. The value of
0147     /// data member jcalib will also be non-NULL.
0148 
0149 
0150 #if !JANA2_HAVE_XERCES
0151     (void) xmlfile; // Suppress unused parameter warning
0152     (void) xml;     // Suppress unused parameter warning
0153     jerr<<endl;
0154     jerr<<"This JANA library was compiled without XERCESC support. To enable"<<endl;
0155     jerr<<"XERCESC, install it on your system and set your XERCESCROOT enviro."<<endl;
0156     jerr<<"variable to point to it. Then, recompile and install JANA."<<endl;
0157     jerr<<endl;
0158 #else
0159     // Initialize XERCES system
0160     XMLPlatformUtils::Initialize();
0161 
0162     // Instantiate the DOM parser.
0163     parser = new XercesDOMParser();
0164     parser->setCreateEntityReferenceNodes(false);
0165     parser->setValidationScheme(XercesDOMParser::Val_Always);
0166     parser->setValidationSchemaFullChecking(true);
0167     parser->setDoSchema(true);
0168     parser->setDoNamespaces(true);    // optional
0169 
0170     // Create an error handler and install it
0171     JGeometryXML::ErrorHandler errorHandler;
0172     parser->setErrorHandler(&errorHandler);
0173 
0174     // EntityResolver is used to keep track of XML filenames so a full MD5 checksum can be made
0175     EntityResolver myEntityResolver(xmlfile, jcalib);
0176     parser->setEntityResolver(&myEntityResolver);
0177 
0178     // Reset document pool
0179     parser->resetDocumentPool();
0180 
0181     // Parse top-level file allowing entity resolver to handle lower levels
0182     if( xml != "" ){
0183         // --- Read lower levels from Calib DB
0184         xercesc::MemBufInputSource myxml_buf((const unsigned char*)xml.c_str(), xml.size(), "myxml (in memory)");
0185         parser->parse(myxml_buf);
0186         md5_checksum = myEntityResolver.GetMD5_checksum();
0187     }else{
0188         // --- Read top and lower levels from files
0189         parser->parse(xmlfile.c_str());
0190         md5_checksum = myEntityResolver.GetMD5_checksum();
0191     }
0192  
0193     // Process xml string
0194 
0195     // Get full DOM
0196     doc = parser->getDocument();
0197 
0198     valid_xmlfile = true;
0199 
0200     // Initialize found_xpaths_mutex
0201     pthread_mutex_init(&found_xpaths_mutex, NULL);
0202 
0203     // Make map of node names to speed up code in SearchTree later
0204     MapNodeNames(doc);
0205 
0206 #endif  // !JANA2_HAVE_XERCES
0207 }
0208 
0209 //---------------------------------
0210 // ~JGeometryXML    (Destructor)
0211 //---------------------------------
0212 JGeometryXML::~JGeometryXML()
0213 {
0214 #if JANA2_HAVE_XERCES
0215     // Release parser and delete any memory it allocated
0216     if(valid_xmlfile){
0217         //parser->release(); // This seems to be causing seg. faults so it is commented out.
0218 
0219         // Shutdown XERCES
0220         XMLPlatformUtils::Terminate();
0221     }
0222 #endif
0223 }
0224 
0225 #if JANA2_HAVE_XERCES
0226 //---------------------------------
0227 // MapNodeNames
0228 //---------------------------------
0229 void JGeometryXML::MapNodeNames(xercesc::DOMNode *current_node)
0230 {
0231     // Record the current node's name
0232     char *tmp = XMLString::transcode(current_node->getNodeName());
0233     node_names[current_node] = string(tmp);
0234     XMLString::release(&tmp);
0235 
0236     // Loop over children of this node and recall ourselves to map their names
0237     for(DOMNode *child = current_node->getFirstChild(); child != 0; child=child->getNextSibling()){
0238         current_node = child;
0239         MapNodeNames(current_node); // attributes are automatically added as the tree is searched
0240     }
0241 }
0242 #endif // JANA2_HAVE_XERCES
0243 
0244 //---------------------------------
0245 // Get
0246 //---------------------------------
0247 bool JGeometryXML::Get(string xpath, string &sval)
0248 {
0249     /// Get the value of the attribute pointed to by the specified xpath
0250     /// and attribute by searching the XML DOM tree. Only the first matching
0251     /// occurance will be returned. The value of xpath may contain restrictions
0252     /// on the attributes anywhere along the node path via the XPATH 1.0
0253     /// specification.
0254 
0255     if(!valid_xmlfile){sval=""; return false;}
0256 
0257 
0258     // Look to see if we have already found the requested xpath.
0259     // doing this speeds up startup when using many threads
0260     pthread_mutex_lock(&found_xpaths_mutex);
0261     map<string, string>::iterator iter = found_xpaths.find(xpath);
0262     if(iter != found_xpaths.end()){
0263         sval = iter->second;
0264         pthread_mutex_unlock(&found_xpaths_mutex);
0265         return true;
0266     }
0267 
0268     // It is tempting here to unlock the found_xpaths_mutex
0269     // mutex, but doing so actually slows things down. This
0270     // is because of 2 things:
0271     //
0272     //   1. Xerces will lock its own mutex anyway so the
0273     //      following code block will still run serially
0274     //
0275     //   2. Keeping the mutex locked blocks all other threads
0276     //      at the point above before checking if the
0277     //      result is cached. They therefore benefit from
0278     //      the cache entry once the mutex is finally released
0279     //      below.
0280 
0281 #if JANA2_HAVE_XERCES
0282 
0283     // XERCES locks its own mutex which causes horrible problems if the thread
0284     // is canceled while it has the lock. Disable cancelibility while here.
0285     int oldstate, oldtype;
0286     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
0287     pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
0288 
0289     // NOTE: If this throws an exception, then we'll leave
0290     // this routine without unlocking found_xpaths_mutex !
0291     multimap<xercesc::DOMNode*, string> attributes;
0292     FindAttributeValues(xpath, attributes, 1);
0293 
0294     // If we found the attribute, copy it to users string
0295     if(attributes.size()>0){
0296         sval = (attributes.begin())->second;
0297 
0298         // Cache the result for use in subsequent calls
0299         found_xpaths[xpath] = sval;
0300     }
0301 
0302     // Unlock the found_xpaths_mutex mutex
0303     pthread_mutex_unlock(&found_xpaths_mutex);
0304 
0305     pthread_setcancelstate(oldstate, NULL);
0306     pthread_setcanceltype(oldtype, NULL);
0307 
0308     if(attributes.size()>0)return true; // return true to say we found it
0309 
0310 #endif
0311 
0312     if( verbose > 0) _DBG_<<"Node or attribute not found for xpath \""<<xpath<<"\"."<<endl;
0313 
0314     // Looks like we failed to find the requested item. Let the caller know.
0315     return false;
0316 }
0317 
0318 //---------------------------------
0319 // Get
0320 //---------------------------------
0321 bool JGeometryXML::Get(string xpath, map<string, string> &svals)
0322 {
0323     /// Get all of the attribute names and values for the specified xpath.
0324     /// Only the first matching
0325     /// occurance will be returned. The value of xpath may contain restrictions
0326     /// on the attributes anywhere along the node path via the XPATH 1.0
0327     /// specification.
0328 
0329 
0330     // Clear out anything that might already be in the svals container
0331     svals.clear();
0332 
0333     if(!valid_xmlfile)return false;
0334 
0335 #if JANA2_HAVE_XERCES
0336 
0337     // XERCES locks its own mutex which causes horrible problems if the thread
0338     // is canceled while it has the lock. Disable cancelibility while here.
0339     int oldstate, oldtype;
0340     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
0341     pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
0342 
0343     multimap<xercesc::DOMNode*, string> attributes;
0344     FindAttributeValues(xpath, attributes, 1);
0345 
0346     // If we found the node, get the attribute list
0347     if(attributes.size()>0)GetAttributes((attributes.begin())->first, svals);
0348 
0349     pthread_setcancelstate(oldstate, NULL);
0350     pthread_setcanceltype(oldtype, NULL);
0351 
0352     if(attributes.size()>0)return true; // return true to say we found it
0353 
0354 #endif
0355 
0356     if( verbose > 0) _DBG_<<"Node or attribute not found for xpath \""<<xpath<<"\"."<<endl;
0357 
0358     // Looks like we failed to find the requested item. Let the caller know.
0359     return false;
0360 }
0361 
0362 //---------------------------------
0363 // GetMultiple
0364 //---------------------------------
0365 bool JGeometryXML::GetMultiple(string xpath, vector<string> &vsval)
0366 {
0367     /// Get the value of the attribute pointed to by the specified xpath
0368     /// and attribute by searching the XML DOM tree. All matching
0369     /// occurances will be returned. The value of xpath may contain restrictions
0370     /// on the attributes anywhere along the node path.
0371 
0372     vsval.clear();
0373 
0374     if(!valid_xmlfile){return false;}
0375 
0376 #if JANA2_HAVE_XERCES
0377 
0378     // XERCES locks its own mutex which causes horrible problems if the thread
0379     // is canceled while it has the lock. Disable cancelibility while here.
0380     int oldstate, oldtype;
0381     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
0382     pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
0383 
0384     multimap<xercesc::DOMNode*, string> attributes;
0385     FindAttributeValues(xpath, attributes, 0);
0386 
0387     multimap<xercesc::DOMNode*, string>::iterator iter = attributes.begin();
0388     for(; iter!=attributes.end(); iter++){
0389         vsval.push_back(iter->second);
0390     }
0391 
0392     pthread_setcancelstate(oldstate, NULL);
0393     pthread_setcanceltype(oldtype, NULL);
0394 
0395 #else
0396     (void) xpath; // Suppress unused parameter warning
0397 #endif
0398 
0399     // Looks like we failed to find the requested item. Let the caller know.
0400     return vsval.size()>0;
0401 }
0402 
0403 //---------------------------------
0404 // GetMultiple
0405 //---------------------------------
0406 bool JGeometryXML::GetMultiple(string xpath, vector<map<string, string> >&vsvals)
0407 {
0408     /// Get the value of the attribute pointed to by the specified xpath
0409     /// and attribute by searching the XML DOM tree. All matching
0410     /// occurances will be returned. The value of xpath may contain restrictions
0411     /// on the attributes anywhere along the node path.
0412 
0413     vsvals.clear();
0414 
0415     if(!valid_xmlfile){return false;}
0416 
0417 #if JANA2_HAVE_XERCES
0418 
0419     // XERCES locks its own mutex which causes horrible problems if the thread
0420     // is canceled while it has the lock. Disable cancelibility while here.
0421     int oldstate, oldtype;
0422     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
0423     pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
0424 
0425     multimap<xercesc::DOMNode*, string> attributes;
0426     FindAttributeValues(xpath, attributes, 0);
0427 
0428     multimap<xercesc::DOMNode*, string>::iterator iter = attributes.begin();
0429     for(; iter!=attributes.end(); iter++){
0430 
0431         DOMNode *node = iter->first;
0432         if(!node)continue;
0433 
0434         map<string, string> svals;
0435         GetAttributes(node, svals);
0436         vsvals.push_back(svals);
0437     }
0438 
0439     pthread_setcancelstate(oldstate, NULL);
0440     pthread_setcanceltype(oldtype, NULL);
0441 #else
0442     (void) xpath; // Suppress unused parameter warning
0443 #endif
0444 
0445     // Looks like we failed to find the requested item. Let the caller know.
0446     return vsvals.size()>0;
0447 }
0448 
0449 //---------------------------------
0450 // GetXPaths
0451 //---------------------------------
0452 void JGeometryXML::GetXPaths(vector<string> &xpaths, ATTR_LEVEL_t level, const string &filter)
0453 {
0454     /// Get all of the xpaths associated with the current geometry. Optionally
0455     /// append the attributes according to the value of level. (See JGeometry.h
0456     /// for valid values).
0457     /// If a non-empty string is passed in for "filter" then it is
0458     /// used to match xpaths that should be kept. If no filter is specified
0459     /// (i.e. an empty string) then all xpaths are returned.
0460 
0461     if(!valid_xmlfile){xpaths.clear(); return;}
0462 
0463 #if JANA2_HAVE_XERCES
0464     AddNodeToList((DOMNode*)doc->getDocumentElement(), "", xpaths, level);
0465 #else
0466     (void) level; // Suppress unused parameter warning
0467 #endif // XERCESC
0468 
0469     // If no filter is specified then return now.
0470     if(filter=="")return;
0471 
0472     // Parse the filter xpath
0473     vector<node_t> filter_nodes;
0474     string dummy_str;
0475     unsigned int dummy_int;
0476     ParseXPath(filter, filter_nodes, dummy_str, dummy_int);
0477 
0478     // We need at least one node on the filter to compare to
0479     if(filter_nodes.size()==0)return;
0480 
0481     // Loop over all xpaths
0482     vector<string> xpaths_to_keep;
0483     for(unsigned int i=0; i<xpaths.size(); i++){
0484         // Parse this xpath
0485         vector<node_t> nodes;
0486         ParseXPath(xpaths[i], nodes, dummy_str, dummy_int);
0487 
0488         // Loop over nodes of this xpath
0489         vector<node_t>::iterator iter;
0490         for(iter=nodes.begin(); iter!=nodes.end(); iter++){
0491             if(NodeCompare(filter_nodes.begin(), filter_nodes.end(), iter, nodes.end()))xpaths_to_keep.push_back(xpaths[i]);
0492         }
0493     }
0494 
0495     xpaths = xpaths_to_keep;
0496 }
0497 
0498 //---------------------------------
0499 // NodeCompare
0500 //---------------------------------
0501 bool JGeometryXML::NodeCompare(node_iter_t iter1, node_iter_t end1, node_iter_t iter2, node_iter_t end2)
0502 {
0503     /// Loop over nodes starting at iter1 and iter2 through end1 and end2
0504     /// to see if they match. The number of nodes and their names must
0505     /// match as well as the attributes. The attributes themselves are
0506     /// matched in the following way: All attributes appearing in the
0507     /// iter1 to end1 list must also appear in the iter2 to end2 list.
0508     /// The reverse however, need not be true. Furthermore, if the
0509     /// attribute value in the first list is an empty string, then the
0510     /// corresponding attribute value in the second list can be anything.
0511     /// Otherwise, they must be an exact match.
0512 
0513     // Loop over nodes in both lists simultaneously
0514     for(; iter1!=end1; iter1++, iter2++){
0515         // If we hit the end of the second iterator before the first
0516         // then they must not match.
0517         if(iter2==end2)return false;
0518 
0519         // Check node names
0520         if(iter1->first!="*")
0521             if(iter1->first != iter2->first)return false;
0522 
0523         // Loop over attributes for iter1
0524         map<string,string> &attr1 = iter1->second;
0525         map<string,string> &attr2 = iter2->second;
0526         map<string,string>::iterator attr_iter1 = attr1.begin();
0527         for(; attr_iter1!= attr1.end(); attr_iter1++){
0528 
0529             // Check if this attribute exists
0530             map<string,string>::iterator attr_iter2 = attr2.find(attr_iter1->first);
0531             if(attr_iter2==attr2.end())return false;
0532             // Attribute exists in both lists. If non-emtpy string in list 1,
0533             // then verify they match
0534             if(attr_iter1->second!=""){
0535                 if(attr_iter1->second!=attr_iter2->second)return false;
0536             }
0537         }
0538     }
0539 
0540     return true;
0541 }
0542 
0543 
0544 //---------------------------------
0545 // ParseXPath
0546 //---------------------------------
0547 void JGeometryXML::ParseXPath(string xpath, vector<pair<string, map<string,string> > > &nodes, string &attribute, unsigned int &attr_depth) const
0548 {
0549     /// Parse a xpath string to obtain a list of node names and for each,
0550     /// a map of the attributes and their (optional) values. This is a
0551     /// very poor man's substitute for a real XPATH parser. It only works
0552     /// on strings that are of a form such as:
0553     ///
0554     ///  /HDDS/ForwardDC_s[@name='abc' and @id=143]/section[@name]/tubs
0555     ///
0556     /// where the "and"s are completely ignored. The return vector has
0557     /// pair objects for which the
0558     /// node names are the keys(first) and the values are a map containing the
0559     /// attributes specified for that node(second). It is done this way so that
0560     /// the order of the node names may be maintained in the vector. (Otherwise,
0561     /// one might just use an STL map container rather than a vector of pairs).
0562     /// The attribute maps each have
0563     /// the attribute name as the key and the attribute value as the value.
0564     /// If no attribute value is specified (as for the section node in
0565     /// the above example) then the value is an empty string.
0566     ///
0567     /// This does no checking that the format is valid, even for this
0568     /// very limited syntax. What can I say, it's a poor man's parser ;).
0569 
0570     // Clear attribute string
0571     attribute = "";
0572     attr_depth = 0xFFFFFFFF;
0573 
0574     // First, split path up into strings using "/" as a delimiter
0575     vector<string> sections;
0576     string::size_type lastPos = xpath.find_first_not_of("/", 0);
0577     do{
0578         string::size_type pos = xpath.find_first_of("/", lastPos);
0579         if(pos == string::npos)break;
0580 
0581         sections.push_back(xpath.substr(lastPos, pos-lastPos));
0582 
0583         lastPos = pos+1;
0584     }while(lastPos!=string::npos && lastPos<xpath.size());
0585     sections.push_back(xpath.substr(lastPos, xpath.length()-lastPos));
0586 
0587     // Now split each section into the node name and the attributes list
0588     for(unsigned int i=0; i<sections.size(); i++){
0589         string &str = sections[i];
0590 
0591         // Find the node name
0592         string::size_type pos_node_end = str.find_first_of("[", 0);
0593         if(pos_node_end==string::npos)pos_node_end = str.length();
0594 
0595         // If the node name is prefaced with a namespace, then discard it
0596         string::size_type pos_node_start = str.find_first_of(":", 0);
0597         if(pos_node_start==string::npos || pos_node_start>pos_node_end){
0598             pos_node_start=0;
0599         }else{
0600             pos_node_start++;
0601         }
0602         if(str[pos_node_start]=='@')pos_node_start=pos_node_end;
0603         string nodeName = str.substr(pos_node_start, pos_node_end-pos_node_start);
0604 
0605         // Pull out all of the attributes
0606         map<string,string> qualifiers;
0607         lastPos = str.find_first_of("@", 0);
0608         while(lastPos!=string::npos){
0609             lastPos++; // jump past "@"
0610             string attr="";
0611             string val="";
0612             string::size_type pos_equals = str.find_first_of("=", lastPos);
0613             string::size_type next_attr = str.find_first_of("@", lastPos);
0614             if(pos_equals!=string::npos && (next_attr>pos_equals || next_attr==string::npos)){
0615                 // attribute has "=" in it
0616                 attr = str.substr(lastPos, pos_equals-lastPos);
0617                 string::size_type pos_end = str.find_first_of(" ", pos_equals);
0618                 if(pos_end==string::npos)pos_end = str.size();
0619 
0620                 // For values containing white space, we need to look for both
0621                 // the opening and closing quotes.
0622                 string::size_type pos_quote = str.find_first_of("'", lastPos);
0623                 if(pos_quote!=string::npos){
0624                     pos_quote = str.find_first_of("'", pos_quote+1);
0625                     if(pos_quote!=string::npos && pos_quote>pos_end)pos_end = pos_quote;
0626                 }
0627 
0628                 // At this point, the substring in pos_equals+1 to pos_end
0629                 // may contain quotes and/or a closing bracket "]". We need to
0630                 // identify these and clip them if needed.
0631                 string::size_type pos_start = pos_equals+1;
0632                 if(str[pos_end-1]==']')pos_end--;
0633                 if(str[pos_end-1]=='\'')pos_end--;
0634                 if(str[pos_end-1]=='\"')pos_end--;
0635                 if(str[pos_start]=='\'')pos_start++;
0636                 if(str[pos_start]=='\"')pos_start++;
0637                 val = str.substr(pos_start, pos_end - pos_start);
0638             }else if(pos_equals==string::npos){
0639                 // attribute exists, but does not have "=" in it
0640                 string::size_type pos_end = str.find_first_of(" ", lastPos);
0641                 if(pos_end==string::npos)pos_end = str.length();
0642                 if(str[pos_end-1]==']')pos_end--;
0643                 attr = str.substr(lastPos, pos_end-lastPos);
0644             }else{
0645                 // no more attribute found
0646                 break;
0647             }
0648             if(attr!=""){
0649                 qualifiers[attr] = val;
0650                 lastPos = str.find_first_of("@", lastPos);
0651             }else{
0652                 break;
0653             }
0654         }
0655 
0656         // If this is the last section, it could be specifying only the desired
0657         // attribute and not actually a whole other node. Consider the example:
0658         // '//hdds:element[@name="Antimony"]/@a'
0659         // where the last "@a" means they want the "a" attribute of the
0660         // "element" node. In these cases, we want to add the final attribute
0661         // to the qualifiers list of the previously found node and NOT
0662         // create a a whole other entry in the nodes map.
0663         if(nodeName=="" && i>0){
0664             if(qualifiers.size()==1){
0665                 if(attribute!=""){
0666                     // If we get here then it looks like we have already found a
0667                     // "lone attribute" that is the target of the xpath query.
0668                     // This can happen with an xpath that looks like this:
0669                     //   //mynode/@id/hello/@name
0670                     // This is an error in the xpath so we notify the user
0671                     // but then go ahead and replace the attribute with the
0672                     // current one.
0673                     _DBG_<<"Multiple attribute targets specified in \""<<xpath<<"\""<<endl;
0674                 }
0675                 attribute = qualifiers.begin()->first;
0676                 attr_depth = nodes.size()-1;
0677                 map<string,string> &last_qualifiers = nodes[i-1].second;
0678                 last_qualifiers[attribute] = "";
0679             }
0680         }else{
0681             // Add this node to the list
0682             pair<string, map<string,string> > node(nodeName, qualifiers);
0683             nodes.push_back(node); // This is needed to maintain the order
0684         }
0685     }
0686 
0687 }
0688 
0689 #if JANA2_HAVE_XERCES
0690 //---------------------------------
0691 // AddNodeToList
0692 //---------------------------------
0693 void JGeometryXML::AddNodeToList(xercesc::DOMNode* start, string start_path, vector<string> &xpaths, ATTR_LEVEL_t level)
0694 {
0695     /// This calls itself recursively to walk the DOM tree and find all xpaths
0696     /// corresponding to all of the nodes. It optionally will append
0697     /// the attributes to the nodes in a form compatible with XPATH 1.0
0698     /// such that they can be used (though usually one would want to
0699     /// edit it slightly) as the xpath argument to one of the Get methods.
0700 
0701     // Get name of this node
0702     string nodeName = node_names.at(start);
0703     //char* tmp = XMLString::transcode(start->getNodeName());
0704     //string nodeName = tmp;
0705     //XMLString::release(&tmp);
0706 
0707     // Ignore nodes that start with a "#"
0708     if(nodeName[0] == '#')return;
0709 
0710     // Create map of all attributes of this node
0711     map<string,string> attributes;
0712     if(start->hasAttributes()) {
0713         DOMNamedNodeMap *pAttributes = start->getAttributes();
0714         int nSize = pAttributes->getLength();
0715         for(int i=0;i<nSize;++i) {
0716             DOMAttr *pAttributeNode = (DOMAttr*) pAttributes->item(i);
0717             char *attrName = XMLString::transcode(pAttributeNode->getName());
0718             char *attrValue = XMLString::transcode(pAttributeNode->getValue());
0719             attributes[attrName] = attrValue;
0720             XMLString::release(&attrName);
0721             XMLString::release(&attrValue);
0722         }
0723     }
0724 
0725     // Create attribute qualifier string
0726     string attr_qualifiers = "";
0727     if(level!=attr_level_none && attributes.size()>0){
0728         attr_qualifiers += "[";
0729         map<string,string>::iterator iter = attributes.begin();
0730         for(int i=0; iter!=attributes.end(); i++, iter++){
0731             if(i>0)attr_qualifiers += " and ";
0732             attr_qualifiers += "@"+iter->first+"='"+iter->second+"'";
0733         }
0734         attr_qualifiers += "]";
0735     }
0736 
0737     // Create xpath for this node
0738     string xpath = start_path + "/" + nodeName;
0739     xpaths.push_back(xpath + attr_qualifiers);
0740 
0741     // Add any child nodes of this node
0742     //string current_path = start_path + "/" + nodeName;
0743     for (DOMNode *child = start->getFirstChild(); child != 0; child=child->getNextSibling()){
0744         AddNodeToList(child, xpath + (level==attr_level_all ? attr_qualifiers:""), xpaths, level);
0745     }
0746 }
0747 
0748 //---------------------------------
0749 // FindAttributeValues
0750 //---------------------------------
0751 void JGeometryXML::FindAttributeValues(string &xpath, multimap<DOMNode*, string> &attributes, unsigned int max_values)
0752 {
0753     /// Search the DOM tree and find attributes matching the given xpath
0754 
0755     // First, parse the string to get node names and attribute qualifiers
0756     SearchParameters sp;
0757     ParseXPath(xpath, sp.nodes, sp.attribute_name, sp.attr_depth);
0758 
0759     // Fill in starting values for recursive search
0760     sp.max_values = max_values;
0761     sp.depth = 0;
0762     sp.attr_value = "";
0763     sp.current_node = doc;
0764 
0765     // Do the search. Results are returned in sp.
0766     sp.SearchTree(node_names);
0767 
0768     // Copy search results into user provided container
0769     attributes = sp.attributes;
0770 }
0771 
0772 //---------------------------------
0773 // SearchTree
0774 //---------------------------------
0775 void JGeometryXML::SearchParameters::SearchTree(map<xercesc::DOMNode*, string> &node_names)
0776 {
0777     /// This is a reentrant routine that recursively calls itself while walking the
0778     /// DOM tree, looking for a path that matches the xpath already parsed and
0779     /// passed to us via the "nodes" variable. This examines the node specified
0780     /// by "current_node" and if needed, all of it's children. The value of "depth"
0781     /// specifies which element of the nodes vector we are looking for. On the initial
0782     /// call to this routine, depth will be 0 signifying that we are trying to match
0783     /// the first node. Note that the first level specified in the xpath may not be
0784     /// at the root of the DOM tree so we may recall ourselves at several levels
0785     /// with depth=0 as we try to find the starting point in the DOM tree.
0786     ///
0787     /// The "attr_depth" value specifies which element of the "nodes" vector has
0788     /// the atribute of interest. This is needed since the attribute of interest may
0789     /// reside at any level in the xpath (not necessarily at the end).
0790     ///
0791     /// The "after_node" value is used when looking for multiple nodes that satisfy
0792     /// the same xpath. If this is NULL, then the first matching node encountered
0793     /// is returned. If it is non-NULL, then matching nodes are skipped until
0794     /// the one specified by "after_node" is found. At that point, after_node is
0795     /// set to NULL and the search continued so that the next matching node will be
0796     /// returned. This means for each matching instance, the tree is re-searched
0797     /// from the begining to look for the additional matches which is not very
0798     /// efficient, but it is what it is.
0799 
0800 
0801     // First, make sure "depth" isn't deeper than our nodes map!
0802     if(depth>=nodes.size())return;
0803 
0804     // Get node name in usable format
0805     const string &nodeName = node_names.at(current_node);
0806     //char *tmp = XMLString::transcode(current_node->getNodeName());
0807     //string nodeName = tmp;
0808     //XMLString::release(&tmp);
0809 
0810     // Check if the name of this node matches the one we're looking for
0811     // (specified by depth).
0812     if(nodeName!=nodes[depth].first && nodes[depth].first!="" && nodes[depth].first!="*"){
0813 
0814         // OK, this node is not listed in the xpath explicitly, but it may still
0815         // be an ancestor of the desired xpath. We loop over all children in order
0816         // to check if the specified xpath exists in any of them.
0817 
0818         // If depth is not 0, then we know we're already part-way into
0819         // the tree. Return now since this is not the right branch.
0820         if(depth!=0)return;
0821 
0822         // At this point, we may have just not come across the first node
0823         // specified in our nodes map. Try each of our children
0824         // Loop over children and recall ourselves for each of them
0825         for(DOMNode *child = current_node->getFirstChild(); child != 0; child=child->getNextSibling()){
0826             current_node = child;
0827             SearchTree(node_names); // attributes are automatically added as the tree is searched
0828             if(max_values>0 && attributes.size()>=max_values)return; // bail if max num. of attributes found
0829         }
0830 
0831         // If we get here then we have searched all branches descending from this
0832         // node and any matches that were found have already been added to the
0833         // attributes list. Return now since there is nothing else to do for the
0834         // current node.
0835         return;
0836     }
0837 
0838     // Get list of attributes for this node
0839     map<string,string> my_attributes;
0840     JGeometryXML::GetAttributes(current_node, my_attributes);
0841 
0842     // If we get here then we are at the "depth"-th node in the list. Check all
0843     // attribute qualifiers for this node (if any).
0844     unsigned int Npassed = 0;
0845     map<string, string> &qualifiers = nodes[depth].second;
0846     string my_attr_value = "";
0847     map<string, string>::iterator iter;
0848     for(iter = qualifiers.begin(); iter!=qualifiers.end(); iter++){
0849         const string &attr = iter->first;
0850         const string &val = iter->second;
0851 
0852         // Loop over attributes of this node
0853         map<string, string>::iterator attr_iter;
0854         for(attr_iter = my_attributes.begin(); attr_iter!=my_attributes.end(); attr_iter++){
0855             const string &my_attr = attr_iter->first;
0856             const string &my_val = attr_iter->second;
0857 
0858             // If this is the attribute of interest, remember it so we can copy into attr_value below
0859             if(attr==attribute_name)my_attr_value = my_val;
0860 
0861             // If this matches a qualifier, increment Npassed counter
0862             if(attr == my_attr){
0863                 if(val=="" || val==my_val)Npassed++;
0864                 break;
0865             }
0866         }
0867     }
0868 
0869     // If we didn't pass all of the attribute tests, then return now since this does not match the xpath
0870     if(Npassed != qualifiers.size())return;
0871 
0872     // If we get here AND we're at attr_depth then temporarily record the value of the
0873     // attribute of interest so when we get to the end of the xpath we can add it
0874     // to the list of attributes found.
0875     if(depth==attr_depth)attr_value = my_attr_value;
0876 
0877     // Check if we have found the final node at the end of the xpath
0878     if(depth==nodes.size()-1){
0879         // At this point, we have found a node that completely matches all node names
0880         // and qualifiers. Add this to the list of attributes
0881         attributes.insert(pair<DOMNode*, string>(current_node, attr_value));
0882 
0883         // No further searching of this node is needed.
0884         return;
0885     }
0886 
0887     // At this point, we have verified that the node names and all of
0888     // the qualifiers for each of them up to and including this node
0889     // are correct. Now we need to loop over this node's children and
0890     // have them check against the next level.
0891     DOMNode* save_current = current_node;
0892     depth++;
0893     for (DOMNode *child = current_node->getFirstChild(); child != 0; child=child->getNextSibling()){
0894         current_node = child;
0895         SearchTree(node_names);
0896     }
0897     depth--;
0898     current_node = save_current;
0899 
0900     return;
0901 }
0902 
0903 //---------------------------------
0904 // GetAttributes
0905 //---------------------------------
0906 void JGeometryXML::GetAttributes(xercesc::DOMNode* node, map<string,string> &attributes)
0907 {
0908     attributes.clear();
0909 
0910     if(!node->hasAttributes())return;
0911 
0912     // Loop over attributes of this node
0913     DOMNamedNodeMap *pAttributes = node->getAttributes();
0914     int nSize = pAttributes->getLength();
0915     for(int i=0;i<nSize;++i) {
0916         DOMAttr *pAttributeNode = (DOMAttr*) pAttributes->item(i);
0917         char *tmp1 = XMLString::transcode(pAttributeNode->getName());
0918         char *tmp2 = XMLString::transcode(pAttributeNode->getValue());
0919         attributes[tmp1] = tmp2;
0920         XMLString::release(&tmp1);
0921         XMLString::release(&tmp2);
0922     }
0923 }
0924 
0925 //<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
0926 
0927 //----------------------------------
0928 // EntityResolver (constructor)
0929 //----------------------------------
0930 JGeometryXML::EntityResolver::EntityResolver(const string &xmlFile, JCalibration *jcalib)
0931 {
0932     /// xmlFile may specify full path to actual file or to item in CCDB
0933 
0934     xml_filenames.push_back(xmlFile);
0935     this->jcalib = jcalib;
0936 
0937     // Peel off path part
0938     string fname = xmlFile;
0939     size_t pos = fname.find_last_of('/');
0940     if(pos != string::npos){
0941         path = fname.substr(0,pos) + "/";
0942     }
0943 
0944     PRINT_CHECKSUM_INPUT_FILES = false;
0945     // if(gPARMS){
0946     //     if(gPARMS->Exists("PRINT_CHECKSUM_INPUT_FILES")){
0947     //         gPARMS->GetParameter("PRINT_CHECKSUM_INPUT_FILES", PRINT_CHECKSUM_INPUT_FILES);
0948     //     }
0949     // }
0950 }
0951 
0952 //----------------------------------
0953 // EntityResolver (destructor)
0954 //----------------------------------
0955 JGeometryXML::EntityResolver::~EntityResolver()
0956 {
0957 
0958 }
0959 
0960 //----------------------------------
0961 // resolveEntity
0962 //----------------------------------
0963 xercesc::InputSource* JGeometryXML::EntityResolver::resolveEntity(const XMLCh* const publicId, const XMLCh* const systemId)
0964 {
0965     /// This method gets called from the xerces parser each time it
0966     /// opens a file (except for the top-level file). For each of these,
0967     /// record the name of the file being opened. If jcalib is not NULL,
0968     /// then try reading from the calib DB. Otherwise, just return NULL
0969     /// to have xerces handle opening the file in the normal way.
0970 
0971     // Do some backflips to get strings into std::string format
0972     std::string my_publicId = "";
0973     std::string my_systemId = "";
0974     if(publicId){
0975         char *my_publicId_ptr = xercesc::XMLString::transcode(publicId);
0976         my_publicId = my_publicId_ptr;
0977         xercesc::XMLString::release(&my_publicId_ptr);
0978     }
0979     if(systemId){
0980         char *my_systemId_ptr = xercesc::XMLString::transcode(systemId);
0981         my_systemId = my_systemId_ptr;
0982         xercesc::XMLString::release(&my_systemId_ptr);
0983     }
0984 
0985     // The systemId seems to be the one we want
0986     string fullpath = path + my_systemId;
0987     xml_filenames.push_back(fullpath);
0988 
0989     if(jcalib){
0990         // Read from Calib DB
0991         vector< map<string, string> > vals;
0992         jcalib->GetCalib(fullpath, vals);
0993         if(vals.empty()){
0994             jerr << " Failed to get XML from Calib DB for " << fullpath << "! Geometry info not available!!" << endl;
0995             exit(-1);
0996         }
0997         string &xml = vals[0].begin()->second;
0998 
0999         // Here we save the xml string in a member container so that it persists in
1000         // memory through the life of this resolver object. It was observed that sometimes
1001         // not all entities were parsed when a stack variable was used since MemBufInputSource
1002         // apparently does not make a copy of the data.
1003         xml_content.push_back(xml);
1004         auto *myxml_buf = new xercesc::MemBufInputSource((const unsigned char*)xml_content.back().c_str(), xml_content.back().size(), "dummy string");
1005         return myxml_buf;
1006     }else{
1007         // Read from file
1008         return NULL; // have xerces handle this using its defaults
1009     }
1010 }
1011 
1012 //----------------------------------
1013 // GetXMLFilenames
1014 //----------------------------------
1015 std::vector<std::string> JGeometryXML::EntityResolver::GetXMLFilenames(void)
1016 {
1017     return xml_filenames;
1018 }
1019 
1020 //----------------------------------
1021 // GetMD5_checksum
1022 //----------------------------------
1023 std::string JGeometryXML::EntityResolver::GetMD5_checksum(void)
1024 {
1025     /// This will calculate an MD5 checksum using all of the files currently
1026     /// in the list of XML files. To do this, it opens each file and reads it
1027     /// in, in its entirety, updating the checksum as it goes. The checksum is
1028     /// returned as a hexadecimal string.
1029 
1030     md5_state_t pms;
1031     md5_init(&pms);
1032     for(string fullpath : xml_filenames){
1033 
1034         uint32_t fsize = 0;
1035 
1036         // Read from Calib DB or from file
1037         if(jcalib){
1038             vector< map<string, string> > vals;
1039             jcalib->GetCalib(fullpath, vals);
1040             if( !vals.empty() ){
1041                 string &xml = vals[0].begin()->second;
1042                 md5_append(&pms, (const md5_byte_t *)xml.c_str(), xml.size());
1043                 fsize = xml.size();
1044             }
1045         }else{
1046             ifstream ifs(fullpath);
1047             if(!ifs.is_open())continue;
1048 
1049             // get length of file:
1050             ifs.seekg (0, ios::end);
1051             unsigned int length = ifs.tellg();
1052             ifs.seekg (0, ios::beg);
1053 
1054             // allocate memory:
1055             char *buff = new char [length];
1056 
1057             // read data as a block:
1058             ifs.read (buff,length);
1059             ifs.close();
1060 
1061             md5_append(&pms, (const md5_byte_t *)buff, length);
1062             fsize = length;
1063 
1064             delete[] buff;
1065         }
1066 
1067         if(PRINT_CHECKSUM_INPUT_FILES) cerr << " .... Adding file to MD5 checksum : " << fullpath <<" (" << fsize << " bytes)" << endl;
1068 
1069     }
1070 
1071     md5_byte_t digest[16];
1072     md5_finish(&pms, digest);
1073 
1074     const size_t str_len = 16*2 + 1;
1075     char hex_output[str_len];
1076     for(int di = 0; di < 16; ++di) {
1077         size_t buff_left = str_len - di * 2;
1078         snprintf(hex_output + di * 2, buff_left, "%02x", digest[di]);
1079     }
1080 
1081     return hex_output;
1082 }
1083 
1084 
1085 #endif // XERCESC
1086