def parse_controls(control_path, control_ids, relationship_ids, framework_id):
    """parse the NIST800-53 revision 4 controls and return a STIX bundle
    :param control_path: the filepath to the controls TSV file
    :param control_ids: is a dict of format {control_name: stixID} which maps
                        control names (e.g AC-1) to desired STIX IDs
    :param relationship_ids: is a dict of format {relationship-source-id---relationship-target-id: relationship-id},
                        same general purpose as control_ids
    :param framework_id: the framework id - e.g., "NIST 800-53 Revision 4"
    """

    tqdmformat = "{desc}: {percentage:3.0f}% |{bar}| {elapsed}<{remaining}{postfix}"

    controls_df = pd.read_csv(control_path, sep="\t", keep_default_na=False, header=0)

    controls = []
    current_control = None
    for index, row in tqdm(list(controls_df.iterrows()), desc="parsing NIST 800-53 revision 4", bar_format=tqdmformat):
        rowtype = row_type(row)

        if rowtype == "control":
            controls.append(Control(row, control_ids))
            current_control = controls[-1]  # track current control to pass to enhancements
        if rowtype == "control_enhancement":
            controls.append(Control(row, control_ids, parent=current_control))
        if rowtype == "statement":
            controls[-1].add_statement(row)
        if rowtype == "substatement":
            controls[-1].add_substatement(row)

    # parse controls into stix
    stix_controls = []
    for control in tqdm(controls, desc="creating controls", bar_format=tqdmformat):
        stix_controls.append(control.to_stix(framework_id))

    # parse control relationships into stix
    relationships = []
    for control in tqdm(list(filter(lambda c: control.parent_id or len(control.related) > 0, controls)),
                        desc="creating control relationships",
                        bar_format=tqdmformat):
        if control.parent_id:
            # build subcontrol-of relationships
            target_id = control_ids[control.parent_id]
            source_id = control.stix_id
            joined_id = f"{source_id}---{target_id}"
            rel_type = "subcontrol-of"
            subcontrols_refs = relationship_ids.get(rel_type, {})

            relationships.append(Relationship(
                id=subcontrols_refs[joined_id] if joined_id in subcontrols_refs else None,
                source_ref=source_id,
                target_ref=target_id,
                relationship_type=rel_type
            ))

        if len(control.related) > 0:
            # build related-to relationships
            for related_id in control.related:
                if related_id not in control_ids:
                    continue  # sometimes related doesn't refer to a control but rather an appendix section
                source_id = control.stix_id
                target_id = control_ids[related_id]
                joined_id = f"{source_id}---{target_id}"
                rel_type = "related-to"
                related_refs = relationship_ids.get(rel_type, {})

                relationships.append(Relationship(
                    id=related_refs[joined_id] if joined_id in related_refs else None,
                    source_ref=source_id,
                    target_ref=target_id,
                    relationship_type=rel_type
                ))

    return Bundle(*itertools.chain(stix_controls, relationships), allow_custom=True)
parse the NIST800-53 revision 4 controls and return a STIX bundle
    :param control_path: the filepath to the controls TSV file
    :param control_ids: is a dict of format {control_name: stixID} which maps
                        control names (e.g AC-1) to desired STIX IDs
    :param relationship_ids: is a dict of format {relationship-source-id---relationship-target-id: relationship-id},
                        same general purpose as control_ids
    :param framework_id: the framework id - e.g., "NIST 800-53 Revision 4"
    
src/parse_r4_controls.py