Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

I'm editing a scrip from ESRI to try and create a list of all datasources within MXD's. I've got the script to crawl the file tree and find all the info, but I would like it to output the data for each layer/datasource on a single line, so it is easy to sort in excel. If anyone could take a look at the code I have so far and let me know how to accomplish that, I would appreciate it.

import arcpy, datetime, os

try:

arcpy.gp.overwriteOutput = True

#Read input parameters from GP dialog
folderPath = arcpy.GetParameterAsText(0)
output = arcpy.GetParameterAsText(1)

#Create an output file
outFile = open(output, "w")

#Loop through each MXD file
count = 0
for root, dirs, files in os.walk(folderPath):
    for filename in os.listdir(folderPath):
      fullpath = os.path.join(folderPath, filename)
      if os.path.isfile(fullpath):
        if filename.lower().endswith(".mxd"):

            #Reference MXD
            mxd = arcpy.mapping.MapDocument(fullpath)
            count = 1

            #Write MXD data to file
            MapDoc = os.path.basename(mxd.filePath)
            MapDocPath = mxd.filePath 

            #Reference each data frame and report data
            DFList = arcpy.mapping.ListDataFrames(mxd)
            for df in DFList:
                #Format output values
                if df.description == "": descValue = "None"
                else: descValue = df.description


                #Reference each layer in a data frame
                lyrList = arcpy.mapping.ListLayers(mxd, "", df)
                for lyr in lyrList:
                    lyrName = lyr.name 
                    if lyr.supports("dataSource"):
                        lyrDatasource = lyr.dataSource                             
                    else: lyrDatasource = "N/A"

            outFile.write("Map Document, MXD Path, DataFrame List, Description, Layer name, Layer Datasource")
            str = ",";
            seq = (MapDoc, MapDocPath, DFList, descValue, lyrName, lyrDatasource);
            outFile.write(str.join(seq))

            del mxd                      

outFile.close()

#Open resulting text file
os.startfile(output)

#Delete variables that reference data on disk
del folderPath, output, outFile, fullpath

except Exception, e:
import traceback
map(arcpy.AddError, traceback.format_exc().split("\n"))
arcpy.AddError(str(e))

The error I recieve when I try to run this is as follows:

\MultiMXDReport_crawl.py", line 60, in <module>
outFile.write(str.join(seq))
TypeError: sequence item 2: expected string or Unicode, list found
<type 'exceptions.TypeError'>: 'str' object is not callable
Failed to execute (MultiMXDCrawler).
share|improve this question
    
Can you provide a link of where you got the ESRI script from? –  artwork21 Jul 31 '12 at 15:43
    
    
Here is the original script in question: pastebin.com/P5NNWbXa –  blah238 Jul 31 '12 at 17:21
    
Pay attention to PEP8 to improve your style python.org/dev/peps/pep-0008 –  Pablo Jul 31 '12 at 20:45
    
Yes, please don't lean too much on ESRI examples when learning how to code... very rarely do I see their samples promote good practices as far as coding styles. –  blah238 Aug 1 '12 at 3:22

3 Answers 3

Two main problems I see with your script:

One is you need to indent properly. Indentation is part of the syntax and logic in Python, and because your seq variable assignment and outFile.write call is declared outside of the innermost loop (where you iterate through each layer in each data frame) you will not get the desired result.

The other is that you reference a list variable in your seq assignment and then attempt to join the sequence elements. This is not possible because join expects string or unicode objects for the sequence elements. Therefore you will first need to convert your DFList variable to a string by joining again, or more likely what you want is to just reference the current df variable's name property.

Stylistically and practically I would consider breaking your script up into functions, as well as using the csv module to generate a CSV file. Consider using a generator function to produce the rows of data you want to send to the CSV writer.

Here is a version of the script that uses the csv module and a generator function:

import arcpy, os, csv

def main(folder, outputfile):
    with open(outputfile, "wb") as f:
        w = csv.writer(f)
        header = ("Map Document", "MXD Path", "DataFrame Name", "DataFrame Description", "Layer name", "Layer Datasource")
        w.writerow(header)
        rows = crawlmxds(folder)
        w.writerows(rows)

def crawlmxds(folder):
    for root, dirs, files in os.walk(folder):
        for f in files:
            if f.lower().endswith(".mxd"):
                mxdName = os.path.splitext(f)[0]
                mxdPath = os.path.join(root, f)
                mxd = arcpy.mapping.MapDocument(mxdPath)
                dataframes = arcpy.mapping.ListDataFrames(mxd)
                for df in dataframes:
                    dfDesc = df.description if df.description != "" else "None"
                    layers = arcpy.mapping.ListLayers(mxd, "", df)
                    for lyr in layers:
                        lyrName = lyr.name
                        lyrDatasource = lyr.dataSource if lyr.supports("dataSource") else "N/A"
                        seq = (mxdName, mxdPath, df.name, dfDesc, lyrName, lyrDatasource);
                        yield seq
                del mxd

if __name__ == "__main__":
    folderPath = r"C:\Program Files (x86)\ArcGIS\DeveloperKit10.0\Samples\data" # or arcpy.GetParameterAsText(0)
    output = r"c:\GISData\mxdcrawler.csv" # or arcpy.GetParameterAsText(1)
    main(folderPath, output)
share|improve this answer

Been a while since I did any python (I mostly use C# these days) however just a quick glance at your code tells me that your trying to join an array of strings in your sequence.

Essentially your taking:

[string1][string2][string3]

and expecting it to behave like

string1

or to be more specific:

[[field1][field2][field3]] [[field1][field2][field3]] [[field1][field2][field3]]

when what you need is

[[field1][field2][field3]]

being repeated each time so that you end up with

field1,field2,field3 . . . field1,field2,field3

where you have your seq = (...) line, you essentially want to loop over that sequence like so

repeat loop for each line of Mapdoc,MapdocPath,DFList,descvalue,lyrname,lyrDatasource concatenating each field with a , write that line to a file with a newline end loop when no more lines left

at the moment your simply trying to

take each array element join each array element with , as if each element was a string end loop when all array elements have been joined write entire long string in one go to file

by the way I know this is a bit of a strange answer, but as I say it's a long time since Iv'e done any python and I don't have an interpreter to hand to test any code, I can see from the program flow though what's going on, and if you split your bigger array into lots of smaller arrays, then join each of those smaller ones in turn to form a single line that should solve your problem.

share|improve this answer
  1. While not strictly a reserved word, "str" is the name of a built in python type (and function). Don't use it as a variable name.

    >>> print str, str(1234)
    <type 'str'> 1234
    >>> str='somestring'
    >>> print str, str(1234)
    somestringTraceback (most recent call last):
      File "<interactive input>", line 1, in <module>
    TypeError: 'str' object is not callable
    
  2. DFList is a list, lists (within lists) can't be joined, only strings can. i.e.

    >>> print ','.join(['astring',['alist','withstrings']])
    Traceback (most recent call last):
      File "<interactive input>", line 1, in <module>
    TypeError: sequence item 1: expected string, list found
    
    >>> print ','.join(['astring','anotherstring','laststring'])
    astring,anotherstring,laststring
    

    If you need to join a list of lists, use list comprehension

    >>> lst=['1',['2','3'],'4']
    >>> print ','.join(['somestring'.join(l) for l in lst])
    1,2somestring3,4
    
  3. DFList is a list of dataframe objects. It doesn't really make sense trying to write that out to a file:

    >>> print DFList
    [<DataFrame object at 0x60f48d0[0x6102d40]>, <DataFrame object etc...]>]
    
  4. Also, I think you probably intended to write just the name and description of the dataframe that a given layer is in, not all dataframes in the mxd, so the following code makes a bit more sense (though could definitely be improved as per other answers in this thread):

    import arcpy, datetime, os
    
    arcpy.gp.overwriteOutput = True
    
    #Read input parameters from GP dialog
    folderPath = 'd:/temp'#arcpy.GetParameterAsText(0)
    output = 'd:/temp/test.txt' #arcpy.GetParameterAsText(1)
    
    #Create an output file
    outFile = open(output, "w")
    #==== Moved to before loop so you only write the headers once, note linebreak '\n' character
    outFile.write("Map Document,MXD Path,DataFrame Name,DataFrame Description,Layer name,Layer Datasource\n")
    
    #Loop through each MXD file
    for root, dirs, files in os.walk(folderPath):
        for file in files: # files is a list of files in the current directory
            if file.lower().endswith(".mxd"):
                fullpath = os.path.join(root, file) # root is the current directory
                #Reference MXD
                mxd = arcpy.mapping.MapDocument(fullpath)
    
                #Write MXD data to file
                MapDoc = os.path.basename(mxd.filePath)
                MapDocPath = mxd.filePath
    
                #Reference each data frame and report data
                DFList = arcpy.mapping.ListDataFrames(mxd)
                for df in DFList:
                    #Format output values
                    if df.description == "": descValue = "None"
                    else: descValue = df.description
                    #==== Note the new descName variable
                    descName=df.name
    
                    #Reference each layer in a data frame
                    lyrList = arcpy.mapping.ListLayers(mxd, "", df)
                    for lyr in lyrList:
                        lyrName = lyr.name
                        if lyr.supports("dataSource"):
                            lyrDatasource = lyr.dataSource
                        else: lyrDatasource = "N/A"
                        #==== Got rid of DFList and inserted descName instead
                        seq = (MapDoc, MapDocPath, descName, descValue, lyrName, lyrDatasource);
                        #==== Got rid of "str" variable and just used a string literal - ','
                        outFile.write(','.join(seq)+'\n')
                del mxd
    outFile.close()
    
share|improve this answer
    
I made some small changes to your script to get it working -- os.walk was not being utilized properly, and it's still pretty ugly but it does run. –  blah238 Aug 2 '12 at 4:52
    
It's not my script :) Feel free to completely refactor it if you like, haha. I'm itching to, but that would defeat the purpose of what I was trying to show with the minor changes to @Chris' code. –  Luke Aug 2 '12 at 6:17

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.