docs: def listsdm(sdm, file=None):
      """Generate a standard "listsdm" listing of(A)SDM dataset contents.
  
      sdm (str)
        The path to the (A)SDM dataset to parse
      file (stream-like object, such as an opened file)
        Where to print the human-readable listing. If unspecified, results
        go to :data:`sys.stdout`.
      Returns
        A dictionary of information about the dataset. Contents not yet
        documented.
  
      Example::
  
        from pwkit.environments.casa import tasks
        tasks.listsdm('myalmaa.asdm')
  
      This code based on CASA's `task_listsdm.py`, with this version info::
  
        # v1.0: 2010.12.07, M. Krauss
        # v1.1: 2011.02.23, M. Krauss: added functionality for ALMA data
        #
        # Original code based on readscans.py, courtesy S. Meyers
  
      """
      from xml.dom import minidom
      import string
  
      def printf(fmt, *args):
          if len(args):
              s = fmt % args
          else:
              s = str(fmt)
          print(s, file=file)
  
      qa = util.tools.quanta()
      me = util.tools.measures()
  
      list_scans = True
      list_antennas = False
      list_fields = True
      list_spws = False
  
      # read Scan.xml
      xmlscans = minidom.parse(sdm+'/Scan.xml')
      scandict = {}
      startTimeShort = []
      endTimeShort = []
      rowlist = xmlscans.getElementsByTagName('row')
      for rownode in rowlist:
          rowfid = rownode.getElementsByTagName('scanNumber')
          fid = int(rowfid[0].childNodes[0].nodeValue)
          scandict[fid] = {}
  
          # number of subscans
          rowsubs = rownode.getElementsByTagName('numSubscan')
          if len(rowsubs) == 0:
              # EVLA and old ALMA data
              rowsubs = rownode.getElementsByTagName('numSubScan')
          nsubs = int(rowsubs[0].childNodes[0].nodeValue)
  
          # intents
          rownint = rownode.getElementsByTagName('numIntent')
          nint = int(rownint[0].childNodes[0].nodeValue)
  
          rowintents = rownode.getElementsByTagName('scanIntent')
          sint = str(rowintents[0].childNodes[0].nodeValue)
          sints = sint.split()
          rint = ''
          for r in range(nint):
              intent = sints[2+r]
              if rint=='':
                  rint = intent
              else:
                  rint += ' '+intent
  
          # start and end times in mjd ns
          rowstart = rownode.getElementsByTagName('startTime')
          start = int(rowstart[0].childNodes[0].nodeValue)
          startmjd = float(start)*1.0E-9/86400.0
          t = b(qa.quantity(startmjd,b'd'))
          starttime = qa.time(t,form=b'ymd',prec=8)[0]
          startTimeShort.append(qa.time(t,prec=8)[0])
          rowend = rownode.getElementsByTagName('endTime')
          end = int(rowend[0].childNodes[0].nodeValue)
          endmjd = float(end)*1.0E-9/86400.0
          t = b(qa.quantity(endmjd,b'd'))
          endtime = qa.time(t,form=b'ymd',prec=8)[0]
          endTimeShort.append(qa.time(t,prec=8)[0])
  
          # source name
          rowsrc = rownode.getElementsByTagName('sourceName')
          try:
              src = str(rowsrc[0].childNodes[0].nodeValue)
          except:
              src = '???' # PKGW
  
          scandict[fid]['start'] = starttime
          scandict[fid]['end'] = endtime
          timestr = starttime+'~'+endtime
          scandict[fid]['timerange'] = timestr
          scandict[fid]['source'] = src
          scandict[fid]['intent'] = rint
          scandict[fid]['nsubs'] = nsubs
  
      # read Main.xml
      xmlmain = minidom.parse(sdm+'/Main.xml')
      rowlist = xmlmain.getElementsByTagName('row')
      mainScanList = []
      mainConfigList = []
      fieldIdList = []
      for rownode in rowlist:
  
          # get the scan numbers
          rowfid = rownode.getElementsByTagName('scanNumber')
          fid = int(rowfid[0].childNodes[0].nodeValue)
          mainScanList.append(fid)
  
          # get the configuration description
          rowconfig = rownode.getElementsByTagName('configDescriptionId')
          config = str(rowconfig[0].childNodes[0].nodeValue)
          mainConfigList.append(config)
  
          # get the field ID
          rowfieldid = rownode.getElementsByTagName('fieldId')
          fieldid = string.split(str(rowfieldid[0].childNodes[0].nodeValue), '_')[1]
          fieldIdList.append(fieldid)
  
      # read ConfigDescription.xml to relate the configuration
      # description to a(set) of data description IDs
      xmlconfig = minidom.parse(sdm+'/ConfigDescription.xml')
      rowlist = xmlconfig.getElementsByTagName('row')
      configDescList = []
      dataDescList = []
      for rownode in rowlist:
  
          # get the configuration description
          rowConfigDesc = rownode.getElementsByTagName('configDescriptionId')
          configDesc = str(rowConfigDesc[0].childNodes[0].nodeValue)
          configDescList.append(configDesc)
  
          # make a list of the data description IDs:
          rowNumDataDesc = rownode.getElementsByTagName('numDataDescription')
          numDataDesc = int(rowNumDataDesc[0].childNodes[0].nodeValue)
  
          rowDataDesc = rownode.getElementsByTagName('dataDescriptionId')
          dataDescStr = str(rowDataDesc[0].childNodes[0].nodeValue)
          dataDescSplit = dataDescStr.split()
          dataDesc = []
          for i in range(numDataDesc):
              dataDesc.append(dataDescSplit[i+2])
          dataDescList.append(dataDesc)
  
      # read DataDescription.xml to relate the data description IDs to
      # spectral window IDs
      xmlDataDesc = minidom.parse(sdm+'/DataDescription.xml')
      rowlist = xmlDataDesc.getElementsByTagName('row')
      dataDescElList = []
      spwIdDataDescList = []
      for rownode in rowlist:
  
          # get the data description ID, make another list:
          rowDataDescEl = rownode.getElementsByTagName('dataDescriptionId')
          dataDescEl = str(rowDataDescEl[0].childNodes[0].nodeValue)
          dataDescElList.append(dataDescEl)
  
          # get the related spectral window ID:
          rowSpwIdDataDesc = rownode.getElementsByTagName('spectralWindowId')
          spwIdDataDesc = str(rowSpwIdDataDesc[0].childNodes[0].nodeValue)
          spwIdDataDescList.append(spwIdDataDesc)
  
      # read SpectralWindow.xml, get information about number of
      # channels, reference frequency, baseband name, channel width.
      # Interesting that there seem to be multiple fields that give the
      # same information: chanFreqStart=reFreq,
      # chanFreqStep=chanWidth=resolution.  Why?(Note: all units are Hz)
      # Note: this is where the script breaks for ALMA data, since there
      # are different tags in SpectraWindow.xml(for varying channel widths).
      xmlSpecWin = minidom.parse(sdm+'/SpectralWindow.xml')
      rowlist = xmlSpecWin.getElementsByTagName('row')
      spwIdList = []
      nChanList = []
      refFreqList = []
      chanWidthList = []
      basebandList = []
      for rownode in rowlist:
  
          # get the various row values:
          rowSpwId = rownode.getElementsByTagName('spectralWindowId')
          rowNChan = rownode.getElementsByTagName('numChan')
          rowRefFreq = rownode.getElementsByTagName('refFreq')
          # For EVLA
          rowChanWidth = rownode.getElementsByTagName('chanWidth')
          # For ALMA
          rowChanWidthArr = rownode.getElementsByTagName('chanWidthArray')
          rowBaseband = rownode.getElementsByTagName('basebandName')
  
          # convert to values or strings and append to the relevant lists:
          spwId = str(rowSpwId[0].childNodes[0].nodeValue)
          spwIdList.append(spwId)
          nChan = int(rowNChan[0].childNodes[0].nodeValue)
          nChanList.append(nChan)
          refFreq = float(rowRefFreq[0].childNodes[0].nodeValue)
          refFreqList.append(refFreq)
          if rowChanWidth:
              chanWidth = float(rowChanWidth[0].childNodes[0].nodeValue)
              chanWidthList.append(chanWidth)
          if rowChanWidthArr:
              tmpArr = str(rowChanWidthArr[0].childNodes[0].nodeValue).split(' ')
              tmpWidth = []
              for cw in range(2, len(tmpArr)):
                  thisWidth = float(tmpArr[cw])
                  tmpWidth.append(thisWidth)
              chanWidthList.append(tmpWidth)
          baseband = str(rowBaseband[0].childNodes[0].nodeValue)
          basebandList.append(baseband)
  
      # read Field.xml
      xmlField = minidom.parse(sdm+'/Field.xml')
      rowlist = xmlField.getElementsByTagName('row')
      fieldList = []
      fieldNameList = []
      fieldCodeList = []
      fieldRAList = []
      fieldDecList = []
      fieldSrcIDList = []
      for rownode in rowlist:
          rowField = rownode.getElementsByTagName('fieldId')
          rowName = rownode.getElementsByTagName('fieldName')
          rowCode = rownode.getElementsByTagName('code')
          rowCoords = rownode.getElementsByTagName('referenceDir')
          rowSrcId = rownode.getElementsByTagName('sourceId')
  
          # convert to values or strings and append to relevent lists:
          fieldList.append(int(string.split(str(rowField[0].childNodes[0].nodeValue),'_')[1]))
          fieldNameList.append(str(rowName[0].childNodes[0].nodeValue))
          fieldCodeList.append(str(rowCode[0].childNodes[0].nodeValue))
          coordInfo = rowCoords[0].childNodes[0].nodeValue.split()
          RADeg = float(coordInfo[3])* (180.0/np.pi)
          DecDeg = float(coordInfo[4])* (180.0/np.pi)
          RAInp = {'unit': 'deg', 'value': RADeg}
          DecInp = {'unit': 'deg', 'value': DecDeg}
          RAHMS = b(qa.formxxx(b(RAInp), format=b'hms'))
          DecDMS = b(qa.formxxx(b(DecInp), format=b'dms'))
          fieldRAList.append(RAHMS)
          fieldDecList.append(DecDMS)
          fieldSrcIDList.append(int(rowSrcId[0].childNodes[0].nodeValue))
  
      # read Antenna.xml
      xmlAnt = minidom.parse(sdm+'/Antenna.xml')
      rowlist = xmlAnt.getElementsByTagName('row')
      antList = []
      antNameList = []
      dishDiamList = []
      stationList = []
      for rownode in rowlist:
          rowAnt = rownode.getElementsByTagName('antennaId')
          rowAntName = rownode.getElementsByTagName('name')
          rowDishDiam = rownode.getElementsByTagName('dishDiameter')
          rowStation = rownode.getElementsByTagName('stationId')
  
          # convert and append
          antList.append(int(string.split(str(rowAnt[0].childNodes[0].nodeValue), '_')[1]))
          antNameList.append(str(rowAntName[0].childNodes[0].nodeValue))
          dishDiamList.append(float(rowDishDiam[0].childNodes[0].nodeValue))
          stationList.append(str(rowStation[0].childNodes[0].nodeValue))
  
      # read Station.xml
      xmlStation = minidom.parse(sdm+'/Station.xml')
      rowlist = xmlStation.getElementsByTagName('row')
      statIdList = []
      statNameList = []
      statLatList = []
      statLonList = []
      for rownode in rowlist:
          rowStatId = rownode.getElementsByTagName('stationId')
          rowStatName = rownode.getElementsByTagName('name')
          rowStatPos = rownode.getElementsByTagName('position')
  
          # convert and append
          statIdList.append(str(rowStatId[0].childNodes[0].nodeValue))
          statNameList.append(str(rowStatName[0].childNodes[0].nodeValue))
          posInfo = string.split(str(rowStatPos[0].childNodes[0].nodeValue))
          x = b(qa.quantity([float(posInfo[2])], b'm'))
          y = b(qa.quantity([float(posInfo[3])], b'm'))
          z = b(qa.quantity([float(posInfo[4])], b'm'))
          pos = b(me.position(b'ITRF', x, y, z))
          qLon = pos['m0']
          qLat = pos['m1']
          statLatList.append(qa.formxxx(qLat, b'dms', prec=0))
          statLonList.append(qa.formxxx(qLon, b'dms', prec=0))
  
      # associate antennas with stations:
      assocStatList = []
      for station in stationList:
          i = np.where(np.array(statIdList) == station)[0][0]
          assocStatList.append(statNameList[i])
  
      # read ExecBlock.xml
      xmlExecBlock = minidom.parse(sdm+'/ExecBlock.xml')
      rowlist = xmlExecBlock.getElementsByTagName('row')
      sTime = float(rowlist[0].getElementsByTagName('startTime')[0].childNodes[0].nodeValue)*1.0E-9
      eTime = float(rowlist[0].getElementsByTagName('endTime')[0].childNodes[0].nodeValue)*1.0E-9
      # integration time in seconds, start and end times:
      intTime = eTime - sTime
      t = b(qa.quantity(sTime/86400.0, b'd'))
      obsStart = qa.time(t, form=b'ymd', prec=8)[0]
      t = b(qa.quantity(eTime/86400.0, b'd'))
      obsEnd = qa.time(t, form=b'ymd', prec=8)[0]
      # observer name and obs. info:
      observerName = str(rowlist[0].getElementsByTagName('observerName')[0].childNodes[0].nodeValue)
      configName = str(rowlist[0].getElementsByTagName('configName')[0].childNodes[0].nodeValue)
      telescopeName = str(rowlist[0].getElementsByTagName('telescopeName')[0].childNodes[0].nodeValue)
      numAntenna = int(rowlist[0].getElementsByTagName('numAntenna')[0].childNodes[0].nodeValue)
  
      # make lists like the dataDescList for spectral windows & related info:
      spwOrd = []
      nChanOrd = []
      rFreqOrd = []
      cWidthOrd = []
      bbandOrd = []
      for i in range(0, len(configDescList)):
          spwTempList = []
          nChanTempList = []
          rFreqTempList = []
          cWidthTempList = []
          bbandTempList = []
  
          for dDesc in dataDescList[i]:
              el = np.where(np.array(dataDescElList) == dDesc)[0][0]
              spwIdN = spwIdDataDescList[el]
              spwEl = np.where(np.array(spwIdList) == spwIdN)[0][0]
              spwTempList.append(int(string.split(spwIdList[spwEl], '_')[1]))
              nChanTempList.append(nChanList[spwEl])
              rFreqTempList.append(refFreqList[spwEl])
              cWidthTempList.append(chanWidthList[spwEl])
              bbandTempList.append(basebandList[spwEl])
          spwOrd.append(spwTempList)
          nChanOrd.append(nChanTempList)
          rFreqOrd.append(rFreqTempList)
          cWidthOrd.append(cWidthTempList)
          bbandOrd.append(bbandTempList)
  
      # add this info to the scan dictionary:
      for scanNum in scandict:
          spwOrdList = []
          nChanOrdList = []
          rFreqOrdList = []
          cWidthOrdList = []
          bbandOrdList = []
          # scanEl could have multiple elements if subscans are present,
          # or for ALMA data:
          scanEl = np.where(np.array(mainScanList) == scanNum)[0]
          for thisEl in scanEl:
              configEl = mainConfigList[thisEl]
              listEl = np.where(np.array(configDescList) == configEl)[0][0]
              spwOrdList.append(spwOrd[listEl])
              nChanOrdList.append(nChanOrd[listEl])
              rFreqOrdList.append(rFreqOrd[listEl])
              cWidthOrdList.append(cWidthOrd[listEl])
              bbandOrdList.append(bbandOrd[listEl])
          try:
              scandict[scanNum]['field'] = int(fieldIdList[scanEl[0]])
          except:
              scandict[scanNum]['field'] = -1 # PKGW
          scandict[scanNum]['spws'] = spwOrdList
          scandict[scanNum]['nchan'] = nChanOrdList
          scandict[scanNum]['reffreq'] = rFreqOrdList
          scandict[scanNum]['chanwidth'] = cWidthOrdList
          scandict[scanNum]['baseband'] = bbandOrdList
  
      # report information to the logger
      printf('================================================================================')
      printf('   SDM File: %s', sdm)
      printf('================================================================================')
      printf('   Observer: %s', observerName)
      printf('   Facility: %s, %s-configuration', telescopeName, configName)
      printf('      Observed from %s to %s(UTC)', obsStart, obsEnd)
      printf('      Total integration time = %.2f seconds(%.2f hours)', intTime, intTime / 3600)
  
      if list_scans:
          printf(' ')
          printf('Scan listing:')
  
          maxspwlen = 0
  
          for scaninfo in scandict.values():
              SPWs = []
              for spw in scaninfo['spws']:
                  SPWs += spw
  
              scaninfo['spwstr'] = str(list(set(SPWs)))
              maxspwlen = max(maxspwlen, len(scaninfo['spwstr']))
  
          fmt = '  %-25s  %-4s %-5s %-15s %-*s %s'
          printf(fmt, 'Timerange(UTC)', 'Scan', 'FldID', 'FieldName', maxspwlen, 'SpwIDs', 'Intent(s)')
  
          for i,(scanid, scaninfo) in enumerate(scandict.items()):
              printf(fmt, startTimeShort[i] + ' - ' + endTimeShort[i], scanid,
                      scaninfo['field'], scaninfo['source'], maxspwlen,
                      scaninfo['spwstr'], scaninfo['intent'])
  
      if list_spws:
          printf(' ')
          printf('Spectral window information:')
          printf('  SpwID  #Chans  Ch0(MHz)  ChWidth(kHz) TotBW(MHz)  Baseband')
  
          for i in range(0, len(spwIdList)):
              printf(' %s %s %s %s %s %s', string.split(spwIdList[i],
                                                         '_')[1].ljust(4), str(nChanList[i]).ljust(4),
                      str(refFreqList[i]/1e6).ljust(8),
                      str(np.array(chanWidthList[i])/1e3).ljust(8),
                      str(np.array(chanWidthList[i])*nChanList[i]/1e6).ljust(8),
                      basebandList[i].ljust(8))
  
      if list_fields:
          printf(' ')
          printf('Field information:')
          printf('  FldID  Code   Name            RA            Dec             SrcID')
  
          for i in range(0, len(fieldList)):
              printf('  %-6d %-6s %-15s %-13s %-15s %-5d', fieldList[i], fieldCodeList[i],
                      fieldNameList[i], fieldRAList[i], fieldDecList[i],
                      fieldSrcIDList[i])
  
      if list_antennas:
          printf(' ')
          printf('Antennas(%i):' % len(antList))
          printf('  ID    Name   Station   Diam.(m)  Lat.          Long.')
  
          for i in range(0, len(antList)):
              printf(' %s %s %s %s %s %s ', str(antList[i]).ljust(5),
                      antNameList[i].ljust(6), assocStatList[i].ljust(5),
                      str(dishDiamList[i]).ljust(5), statLatList[i].ljust(12),
                      statLonList[i].ljust(12))
  
      # return the scan dictionary
      return scandict