function CACF_CreatePerComponentTypeAuditDataAndWriteToCsv(&$CACFconstants, &$altiumParmsByComponentLines, $compType, &$altiumUserParmNamesForThisCompType, &$altiumParmsForThisCompType, &$altiumItemSysParmValuesByGuid, &$altiumItemUserParmValuesByGuid, &$altiumObsoleteCompsByGuid)
{
    /* Retrieve necessary global constants. */
    $ofs = $CACFconstants["ofs"];
    $revSep = $CACFconstants["revSep"];
    $auditFileExt = $CACFconstants["auditFileExt"];
    $auditObsoleteSuffix = $CACFconstants["auditObsoleteSuffix"];
    $doKeepZeroLengthOutputFiles = $CACFconstants["doKeepZeroLengthOutputFiles"];
    $auditComponentsByTypeFileName = $CACFconstants["auditComponentsByTypeFileName"];
    /** Open audit components-by-type file for writing. **/
    /* Construct filename. */
    $auditComponentsByTypeThisFileName = preg_replace('/FOOBAR/', $compType, $auditComponentsByTypeFileName);
    echo date('H:i:s') . "  Begin creating audit per-component-type csv file \"" . $auditComponentsByTypeThisFileName . "\"...\n";
    /* Open file for writing. */
    $doWriteToCsv = 1;
    $auditComponentsByTypeFile = my_fopen($auditComponentsByTypeThisFileName);
    /* Open another version of this file to describe obsolete components. */
    $auditComponentsByTypeObsoleteFileName = preg_replace("/" . $auditFileExt . "/", $auditObsoleteSuffix . $auditFileExt, $auditComponentsByTypeThisFileName);
    $auditComponentsByTypeObsoleteFile = my_fopen($auditComponentsByTypeObsoleteFileName);
    /* Sort the list of user parms used by this component type. */
    $rc = ksort($altiumUserParmNamesForThisCompType);
    if ($rc == FALSE) {
        my_die("ksort() failed!");
    }
    /** Flag that we need to output column headers for this component type. **/
    $doColumnHeaders = 1;
    /* Loop over all the components belonging to this component type. */
    foreach ($altiumParmsForThisCompType as $ItemRevHrid => $val2) {
        //        echo "ItemRevHrid is ".$ItemRevHrid." val2 is ".$val2."\n";
        /* Retrieve the GUID for this component. */
        $GUID = $altiumParmsForThisCompType[$ItemRevHrid]["FooBarBinBatGUID"];
        /* Extract ItemHRID for this component. */
        $ITEMHRID = $altiumItemSysParmValuesByGuid[$GUID][CACF_AlterAltiumSysParmName("ITEMHRID")];
        /* Extract RevisionId for this component. */
        $REVISIONID = $altiumItemSysParmValuesByGuid[$GUID][CACF_AlterAltiumSysParmName("REVISIONID")];
        /* Strip off the revSep separator to yield a RevisionKey. */
        $RevisionKey = preg_replace("/^{$revSep}/", "", $REVISIONID);
        /* Extract whether this component (as an ItemRev) is obsolete or not. */
        $isObsolete = isset($altiumObsoleteCompsByGuid[$GUID]);
        $ItemRevHrid = $ITEMHRID . $REVISIONID;
        //      echo "In CACF_CreatePerComponentTypeAuditDataAndWriteToCsv(), ItemRevGuid is $GUID, ItemRevHrid is \"$ItemRevHrid\", isObsolete is $isObsolete.\n";
        /** Output column headers if needed. **/
        /* See if we need to output column headers. */
        if ($doColumnHeaders == 1) {
            /* Flag that we are done with column headers. */
            $doColumnHeaders = 0;
            $colHeaderLine = "";
            /* Call CACF_WriteComponentAuditColumnHeaders() to do all the real work. */
            CACF_WriteComponentAuditColumnHeaders(&$CACFconstants, $altiumItemSysParmValuesByGuid[$GUID], $altiumUserParmNamesForThisCompType, $colHeaderLine, $doWriteToCsv, $auditComponentsByTypeFile);
            CACF_WriteComponentAuditColumnHeaders(&$CACFconstants, $altiumItemSysParmValuesByGuid[$GUID], $altiumUserParmNamesForThisCompType, $colHeaderLine, $doWriteToCsv, $auditComponentsByTypeObsoleteFile);
        }
        /* endif need to output column headers. */
        /* See if the per-component audit info needs the column header line. */
        if (!isset($altiumParmsByComponentLines[$ITEMHRID])) {
            /* Initialize array for this HRID. */
            $altiumParmsByComponentLines[$ITEMHRID] = array();
            /* Store filename for this HRID. */
            $altiumParmsByComponentLines[$ITEMHRID]["FooBarBinBatFilename"] = preg_replace('/FOOBAR/', $compType . "_" . $ITEMHRID, $auditComponentsByTypeFileName);
            /* Store column headers for this HRID. */
            $altiumParmsByComponentLines[$ITEMHRID]["FooBarBinBatColHeaders"] = $colHeaderLine;
        }
        /* endif */
        /** Create columns of Altium system parameter values; return in $line variable. **/
        CACF_CreateComponentAuditSysParmValues(&$CACFconstants, &$altiumItemSysParmValuesByGuid[$GUID], &$line);
        /** Create columns of Altium user parameter values. **/
        /* Loop over all the defined Altium user parameter names for this component type. */
        foreach ($altiumUserParmNamesForThisCompType as $PARAMETERNAME => $value) {
            /* Unconditionally print out a field separator. */
            $line = $line . $ofs;
            /* See if this component has a stored item user parameter named $PARAMETERNAME. */
            if (isset($altiumItemUserParmValuesByGuid[$GUID][$PARAMETERNAME])) {
                /* Retrieve the value of this parameter. */
                $PARAMETERVALUE = $altiumItemUserParmValuesByGuid[$GUID][$PARAMETERNAME];
                /* Output the value of this component's user parameter. */
                $line = $line . CACF_RemapReservedChars($PARAMETERVALUE);
                /* Remap any ',' chars in cell to '|' chars! */
            }
            /* endif */
        }
        /* end foreach user parameters */
        /* Write line to file.  Note:  explicitly use DOS (CR/LF) \r\n line endings! */
        /* If this is not an obsolete component, then write it to the normal component audit file. */
        if (!$isObsolete) {
            fputs($auditComponentsByTypeFile, $line . "\r\n");
        } else {
            fputs($auditComponentsByTypeObsoleteFile, $line . "\r\n");
        }
        /* Store line also for use in per-component audit file. */
        //      echo "In CACF_CreatePerComponentTypeAuditDataAndWriteToCsv(), stored line for ITEMHRID \"$ITEMHRID\" REVISIONID \"$REVISIONID\".\n";
        $altiumParmsByComponentLines[$ITEMHRID][$RevisionKey] = $line;
    }
    /* end foreach components of this component type */
    /* Close audit components-by-type file. */
    fclose($auditComponentsByTypeFile);
    fclose($auditComponentsByTypeObsoleteFile);
    echo date('H:i:s') . "  Done writing audit per-component-type file \"" . $auditComponentsByTypeThisFileName . "\"...\n";
    /* See if we have been instructed to delete any 0 length output files which result. */
    if (!$doKeepZeroLengthOutputFiles) {
        /* Attempt to delete the output file if needed. */
        if (filesize($auditComponentsByTypeThisFileName) == 0) {
            unlink($auditComponentsByTypeThisFileName);
        }
        /* Attempt to delete the output file if needed. */
        if (filesize($auditComponentsByTypeObsoleteFileName) == 0) {
            unlink($auditComponentsByTypeObsoleteFileName);
        }
    }
    /* endif */
}
function UCTCF_EnableComponentsForUpdateInCmpLib(&$CACFconstants, &$UCTCFconstants, &$CmpLib, &$altiumItemsByGuid, &$altiumItemRevsByGuid, &$cmpLibUserParmsByGuid, &$cmpLibSysParmsByGuid, &$cmpLibGroupXpathByPath, &$cmpLibCompByItemHrid, &$changedCompsByItemRevGuid)
{
    /* Retrieve necessary global constants. */
    $pathSep = $CACFconstants["pathSep"];
    //  echo "Hello from UCTCF_EnableComponentsForUpdateInCmpLib()\n";
    //  echo "cmpLibCompByPathAndHrid is:\n";
    /* Retrieve constants related to enabling and collapsing nodes in CmpLib XML file. */
    $cXmlStateName = $UCTCFconstants["cXmlStateName"];
    $cXmlStateEnabled = $UCTCFconstants["cXmlStateEnabled"];
    $cXmlStateSemiEnabled = $UCTCFconstants["cXmlStateSemiEnabled"];
    $cXmlStateDisabled = $UCTCFconstants["cXmlStateDisabled"];
    $cXmlCollapsedName = $UCTCFconstants["cXmlCollapsedName"];
    $cXmlCollapsedDisabled = $UCTCFconstants["cXmlCollapsedDisabled"];
    $cXmlCollapsedEnabled = $UCTCFconstants["cXmlCollapsedEnabled"];
    //  print_r($cmpLibCompByItemHrid);
    /* Loop over all components that we need to enable. */
    foreach ($changedCompsByItemRevGuid as $ItemRevGuid => $val) {
        /* Get component item HRID. */
        $ItemHrid = $cmpLibSysParmsByGuid[$ItemRevGuid][CACF_AlterAltiumSysParmName("ITEMHRID")];
        /* Get component comment. */
        $comment = $cmpLibSysParmsByGuid[$ItemRevGuid][CACF_AlterAltiumSysParmName("COMMENT")];
        echo "Attempting to enable component ItemHrid \"{$ItemHrid}\".\n";
        /* Get path of this component. */
        $path = $cmpLibSysParmsByGuid[$ItemRevGuid][CACF_AlterAltiumSysParmName("COMPONENTPATH")];
        echo "path is {$path}\n";
        /** Attempt to enable the component itself. **/
        /* Sanity check. */
        if (!isset($cmpLibCompByItemHrid[$ItemHrid])) {
            my_die('In UCTCF_EnableComponentsForUpdateInCmpLib(), unable to find reference to component XML node!');
        }
        /* Retrieve the xpath query that will get us to the XML node for this component. */
        $xpathQuery = $cmpLibCompByItemHrid[$ItemHrid]["xpath"];
        /* Run xpath query to get XML node for this component. */
        UCTCF_GetXmlNodeFromXpathQuery($CmpLib, $xpathQuery, $resultNode);
        //      echo "resultNode is:\n";
        //      print_r($resultNode);
        /* Attempt to enable this component. */
        $resultNode[$cXmlStateName] = $cXmlStateEnabled;
        //      echo "resultNode is now:\n";
        //      print_r($resultNode);
        /** Attempt to semi-enable all folders that are parents of this component. **/
        /* Loop over all path elements (aka. directories aka. groups) that are parents of this component. */
        do {
            echo "In repeat loop, path is \"{$path}\".\n";
            /* See if the current path exists in the XML file. */
            if (isset($cmpLibGroupXpathByPath[$path])) {
                echo "Attempting to semi-enable this group!\n";
                /* Retrieve the xpath query that will get us to the XML node for this path. */
                $xpathQuery = $cmpLibGroupXpathByPath[$path];
                /* Run xpath query to get XML node for this path. */
                UCTCF_GetXmlNodeFromXpathQuery($CmpLib, $xpathQuery, $resultNode);
                //      echo "resultNode is:\n";
                //      print_r($resultNode);
                /* Attempt to semi-enable this path element. */
                $resultNode[$cXmlStateName] = $cXmlStateSemiEnabled;
                $resultNode[$cXmlCollapsedName] = $cXmlCollapsedEnabled;
            } else {
                echo "path does not exist in XML file:  \"{$path}\".\n";
            }
            /* Strip off last directory from $path. */
            /* Note:  That regex without excessive quoting would be '/[^\\]+\\$/'.
               In other words, replace any (collection of non-backslashes), followed by
               a backslash, at the end of the string, with "". */
            /* FIXME:  Used magic strings instead of $pathSep due to having to escape too much! */
            $path = preg_replace('/[^\\\\]+\\\\$/', "", $path);
            //          echo "In repeat loop, path is now \"$path\".\n";
        } while ($path != "");
    }
    /* end foreach */
}