pythonpython-2.7arcgispython-docx

Checking a checkBox in .docx form with Python using docx module


I am attempting to fill out a word document form with Python 2.7's docx module. I can modify text elements just fine but I am having difficulty figuring out how to check a yes or no checkbox.

How do I go about checking one the the checkboxes in the form. I have tried a few different ways but I think it all comes do to me not know how the docx xml is structured when it comes to check boxes.

Am I able to use the Bookmark property to find a specific checkbox and check it as seen in the picture below?

enter image description here

I have uploaded a copy of the test form to Google Drive here.


Solution

  • Ok, so after much frustration I finally figured out how to check a checkbox. There is a element within a checkbox element that signifies if the box is checked. I am essenially able to create that element with the following function.

    from docx.oxml import OxmlElement
    from docx.oxml.ns import qn
    
    def checkedElement():
        elm = OxmlElement('w:checked')
        elm.set(qn('w:val'),"true")
        return elm
    

    I can find all checkboxes within a table cell with the following function. Since the yes is always the first checkbox in each cell I can set the index for a yes check to 0 and a no check to index 1 and then I can append the checked element within the checkbox element:

    def yesNoCheck(yes_no,tableIdx,coords):
        print coords, yes_no
        if yes_no == 'y':
            index = 0
            x = doc.tables[tableIdx].cell(coords[0],coords[1])._element.xpath('.//w:checkBox')
            x[index].append(checkedElement())
        elif yes_no == 'n':
            index = 1
            x = doc.tables[tableIdx].cell(coords[0],coords[1])._element.xpath('.//w:checkBox')
            x[index].append(checkedElement())
        else:
            print "value was neither yes or no"
            pass
    

    here is my full code that I have written so far. I have a bunch of refactoring to do but it works great as of now. There are two tables in my .docx template and dictionary table1 and table2 contain the cell row and column coordinates. This script is used to fill out a required form using data published from ESRI's Survey123.

    from docx import Document
    from docx.oxml import OxmlElement
    from docx.oxml.ns import qn
    from docx.shared import Inches
    from docx.enum.text import WD_ALIGN_PARAGRAPH
    import arcpy
    import datetime
    import os
    
    table1 = {
        'BusinessName':[2,3],
        'LicenseNumber':[2,14],
        'OwnerName':[3,3],
        'PhoneNumber':[3,14],
        'BusinessAddress':[4,5],
        'County':[4,14],
        'City':[5,1],
        'St':[5,8],
        'Zip':[5,15],
        'LicenceExpired':[6,1], #CheckBox
        'DateExpired':[6,15],
        'LicenceRenewal':[7,1], #CheckBox
        'NumberDisplayed':[8,1], #CheckBox
        'NameAddDisplayed':[10,1], #CheckBox
        'VehicleInfoMatches':[12,1], #CheckBox
        'DischargeValveCapped':[14,1], #CheckBox
        'DischargeValveCapChained':[15,1], #CheckBox
        'HoseDisinfectCarried':[16,1], #CheckBox
        'VehicleAndTankClean':[17,1], #CheckBox
        'FreeOfLeaks':[18,1] #CheckBox
    }
    
    table2 = {
        'LandApplyWaste':[1,1], #Yes/No CheckBox
        'LocationDescriptionAccurate':[6,1], #Yes/No CheckBox
        'LocationDescriptionAccDesc':[6,5], #text
        'Slope':[7,1], #Yes/No CheckBox
        'DistanceNearestResidence':[8,1], #Yes/No CheckBox
        'DistanceNearestWell':[9,1], #Yes/No CheckBox
        'DistanceNearestStreamLakeEtc':[10,1], #Yes/No CheckBox
        'SeptageIncorporated':[11,1], #Yes/No CheckBox
        'InjectedIncorporated':[12,3], #Yes/No CheckBox, dependent on the septage incorporated being yes
        'SeptageStabilized':[13,1], #Yes/No CheckBox
        'HowIsLimeMixed':[14,3], #text dependent on if lime was used
        'ConfiningLayerOrGroundwater':[15,1], #Yes/No CheckBox
        'ConfiningLayerOrGroundwaterDesc':[16,3], #text
        'CropGrown':[17,1], #Yes/No CheckBox
        'CropGrownHowVerified':[19,3], #text
        'LandAppCompliance':[20,1], #Yes/No CheckBox
        'AdditionalComments':[22,3],
        'SignDate':[22,13]
    }
    
    def checkedElement():
        elm = OxmlElement('w:checked')
        elm.set(qn('w:val'),"true")
        return elm
    
    def yesNoCheck(yes_no,tableIdx,coords):
        print coords, yes_no
        if yes_no == 'y':
            index = 0
            x = doc.tables[tableIdx].cell(coords[0],coords[1])._element.xpath('.//w:checkBox')
            x[index].append(checkedElement())
        elif yes_no == 'n':
            index = 1
            x = doc.tables[tableIdx].cell(coords[0],coords[1])._element.xpath('.//w:checkBox')
            x[index].append(checkedElement())
        else:
            print "value was neither yes or no"
            pass
    
    def disposalMethodCheck(method, locationDec):
        vals = {
            'WastewaterTreatmentFacility':[20,1],
            'LandApplication':[22,1],
            'SanitaryLandfill':[24,1],
            'SeptageLagoonOrDryingBed':[26,1]
        }
        if method != None:
            row,col = vals[method]
            checkBoxElm = doc.tables[0].cell(row,col)._element.xpath('.//w:checkBox')[0]
            print "{0} Checked!".format(method)
            checkBoxElm.append(checkedElement())
            editTxt(locationDec,0,[row,6]) 
    
    def editTxt(text, tblIdx, coords, alignment = WD_ALIGN_PARAGRAPH.LEFT, bold=True):
        print text, coords
        field = doc.tables[tblIdx].cell(coords[0],coords[1]).paragraphs[0]
        field.text = text
        field.alignment = alignment
        field.runs[0].font.bold = bold
    
    def addSig(sigJpgPath):
        para = doc.tables[1].row_cells(23)[0].paragraphs[0]
        para.alignment = WD_ALIGN_PARAGRAPH.CENTER
        run = para.add_run()
        run.add_picture(sigJpgPath,width=Inches(1.34),height=Inches(.35))
    
    fc = r"E:\PumperTruckInspectionFeatureClass"
    arcpy.MakeFeatureLayer_management (fc, "PumperTruckInspections")
    attach = r"PumperTruckInspection__ATTACH" #Where signatures are stored
    
    def rows_as_dicts(cursor):
        colnames = cursor.fields
        for row in cursor:
            yield dict(zip(colnames, row))
    
    def dateString(date):
        if date != None:
            d = date.strftime('%m/%d/%Y')
            return d
        else:
            print "no date"
            return ''
    
    def checkBusName(name):
        if name != None:
            return name
        else:
            return 'unknown'
    
    with arcpy.da.SearchCursor(fc, '*') as sc:
        for row in rows_as_dicts(sc):
            doc = Document(r"path\to\TEMPLATE.docx")
    
            t = datetime.datetime.now().strftime('%Y-%m-%d')
            newDocName = checkBusName(row['BusinessName']) + t + '.docx'
    
    
            editTxt(row['BusinessName'],0,table1['BusinessName'])
            editTxt(row['LicenseNumber'],0,table1['LicenseNumber'])
            editTxt(row['OwnerName'],0,table1['OwnerName'])
            editTxt(row['PhoneNumber'],0,table1['PhoneNumber'])
            editTxt(row['BusinessAddress'],0,table1['BusinessAddress'])
            editTxt(row['County'],0,table1['County']) 
            editTxt(row['City'],0,table1['City'])
            editTxt(row['St'],0,table1['St'])
            editTxt(row['Zip'],0,table1['Zip'])
            editTxt(dateString(row['DateExpired']),0,table1['DateExpired'])
            yesNoCheck(row['LicenceExpired'],0, table1['LicenceExpired'])
    
            yesNoCheck(row['LicenceRenewal'],0, table1['LicenceRenewal'])
            yesNoCheck(row['NumberDisplayed'],0, table1['NumberDisplayed'])
            yesNoCheck(row['NameAddDisplayed'],0, table1['NameAddDisplayed'])
            yesNoCheck(row['VehicleInfoMatches'],0, table1['VehicleInfoMatches'])
            yesNoCheck(row['DischargeValveCapped'],0, table1['DischargeValveCapped'])
            yesNoCheck(row['DischargeValveCapChained'],0, table1['DischargeValveCapChained'])
            yesNoCheck(row['HoseDisinfectCarried'],0, table1['HoseDisinfectCarried'])
            yesNoCheck(row['VehicleAndTankClean'],0, table1['VehicleAndTankClean'])
            yesNoCheck(row['FreeOfLeaks'],0, table1['FreeOfLeaks'])
            disposalMethodCheck(row['DisposalMethod'],row['DisposalLocation'])
            if row['DisposalMethod'] == 'LandApplication':
                yesNoCheck(row['LandApplyWaste'],1,table2['LandApplyWaste'])
                yesNoCheck(row['LocationDescriptionAccurate'],1,table2['LocationDescriptionAccurate'])
                editTxt(row['LocationDescriptionAccDesc'],1,table2['LocationDescriptionAccDesc'])
                yesNoCheck(row['Slope'],1,table2['Slope'])
                yesNoCheck(row['DistanceNearestResidence'],1,table2['DistanceNearestResidence'])
    
                yesNoCheck(row['DistanceNearestWell'],1,table2['DistanceNearestWell'])
                yesNoCheck(row['DistanceNearestStreamLakeEtc'],1,table2['DistanceNearestStreamLakeEtc'])
                yesNoCheck(row['SeptageIncorporated'],1,table2['SeptageIncorporated'])
                yesNoCheck(row['InjectedIncorporated'],1,table2['InjectedIncorporated']) #might need a new method since its not yes/no
                yesNoCheck(row['SeptageStabilized'],1,table2['SeptageStabilized'])
                editTxt(row['HowIsLimeMixed'],1,table2['HowIsLimeMixed'])
                yesNoCheck(row['ConfiningLayerOrGroundwater'],1,table2['ConfiningLayerOrGroundwater'])
                editTxt(row['ConfiningLayerOrGroundwaterDescript'],1,table2['ConfiningLayerOrGroundwaterDescript'])
                yesNoCheck(row['CropGrown'],1,table2['CropGrown'])
                editTxt(row['CropGrownHowVerified'],1,table2['CropGrownHowVerified'])
                yesNoCheck(row['LandAppCompliance'],1,table2['LandAppCompliance'])
            editTxt(row['AdditionalComments'],1,table2['AdditionalComments'],bold=False)
            where = "REL_GLOBALID = '{0}'".format(row['GlobalID'])
            from pprint import pprint
            with arcpy.da.SearchCursor(attach,['DATA', 'ATT_NAME', 'ATTACHMENTID'],where_clause=where) as cursor:
                for r in rows_as_dicts(cursor):
                    pprint(r)
                    name = r['ATT_NAME']
                    attachment = r['DATA']
                    if name.split('_')[0] == 'InspectorSignature':
                        imagePath = os.path.join(name.split('_')[0] + "_" + )
                        open(("sig.jpeg"), 'wb').write(attachment.tobytes())
                        addSig("sig.jpeg")
    
                        break
    
            editTxt(dateString(row['SignDate']),1,table2['SignDate'],alignment = WD_ALIGN_PARAGRAPH.CENTER,bold=False)
            doc.save(newDocName)
            del doc