Explanation of generateComparisonTable Function
The generateComparisonTable function takes two CSV data inputs (a main CSV and a modified CSV), compares them, and generates an HTML table to visualize the differences. This function helps in identifying added, deleted, and modified rows between two versions of a dataset.
1. Function Parameters
mainCsvData: The original dataset in CSV format.modifiedCsvData: The modified version of the dataset in CSV format.
2. Convert CSV to JSON
Both CSV inputs are converted into arrays of objects using the csvToJson function (not included in the snippet, but assumed to exist). Each row in the CSV is transformed into a JSON object, where column headers become keys.
3. Create a Map for Faster Lookup
A Map object is created from the modified CSV data using Internal ID as the key. This helps in quick lookups when comparing rows.
const modifiedCsvMap = new Map(modifiedCsvArray.map(row => [row[internalIDCol], row]));
4. Compare Main CSV with Modified CSV
Each row from the main CSV is checked against the modified CSV:
- If an
Internal IDexists inmainCsvArraybut not inmodifiedCsvArray, the row is marked as a delete-action (indicating that the row was removed in the modified dataset). - If an
Internal IDexists inmodifiedCsvArraybut not inmainCsvArray, the row is marked as an add-action (indicating that the row was newly added). - If an
Internal IDis present in both datasets, each field is checked for differences: - If a value has changed, it is recorded, and the row is marked as changed.
- A
keyStatusobject is maintained to store which fields were modified.
if (row1[key] !== row2[key]) {
isChanged = true;
modifiedRow[key] = row2[key]; // Store the new value
keyStatus[key] = "changed"; // Mark the field as changed
}
5. Handling Remaining Rows in Modified CSV
After processing all rows from mainCsvArray, any remaining rows in modifiedCsvMap are treated as new entries (add-action).
modifiedCsvMap.forEach(row2 => {
comparisonResult.push({
internalID: row2[internalIDCol] || null,
status: "add-action",
data: row2
});
});
6. Sorting the Comparison Results
The comparison results are sorted based on the Step Number column to ensure a logical order in the output.
comparisonResult.sort((a, b) => {
const stepA = parseInt(a.data["Step Number"], 10) || 0;
const stepB = parseInt(b.data["Step Number"], 10) || 0;
return stepA - stepB;
});
7. Generating the HTML Table
The function dynamically constructs an HTML table:
- Headers: Extracted from the first row.
- Rows: Each row is generated based on
comparisonResult, with CSS classes applied to visually indicate changes. - Cell Highlighting:
- New rows (
add-action): Highlighted with a specific class. - Deleted rows (
delete-action): Highlighted with a different class. - Modified fields (
changed): Individual cells are marked to show which values changed.
if (keyStatus === "changed") {
cellClass = "changed";
} else if (keyStatus === "") {
cellClass = "";
}
8. Returning the Final HTML Table
The function returns the completed HTML table string, ready for rendering on a webpage.
return tableHTML;
Conclusion
This function is useful for visually comparing CSV files in a structured format. The key features include:
- Efficient comparison using Maps for quick lookups.
- Color-coded highlighting to indicate added, removed, and changed data.
- Sorting by step number to maintain logical order.
This script is a great tool for data auditing, change tracking, and version comparison in web applications.
function generateComparisonTable(mainCsvData, modifiedCsvData) {
try {
const internalIDCol = “Internal ID”; // Assuming “Internal ID” is the header
// Convert both CSV contents into arrays of objects
const mainCsvArray = csvToJson(mainCsvData);
const modifiedCsvArray = csvToJson(modifiedCsvData);
console.log(‘mainCsvArray’,mainCsvArray);
console.log(‘modifiedCsvArray’,modifiedCsvArray);
// Create a Map for the modified CSV for faster lookup by Internal ID
const modifiedCsvMap = new Map(modifiedCsvArray.map(row => [row[internalIDCol], row]));
console.log(‘modifiedCsvMap’, modifiedCsvMap);
// Initialize comparisonResult to store the final results
let comparisonResult = [];
// Compare the main CSV rows with the modified CSV
mainCsvArray.forEach(row1 => {
const internalID = row1[internalIDCol];
// console.log(‘Row 1:’, row1[internalIDCol]);
// If the Internal ID exists in the modified CSV, mark the row as green
let rowClass = “”;
let status = ”;
let row2 = modifiedCsvMap.get(internalID);
let modifiedRow = { …row1 };
let keyStatus = {};
// console.log(‘row2:’, row2);
if (!row2 && internalID !== “”) {
// If Internal ID is missing from the modified CSV, mark the row as red (delete-action)
comparisonResult.push({
internalID,
status: “delete-action”,
data: row1
});
} else if (row2 && internalID === “”) {
// If Internal ID is empty in the modified file, mark as add-action
comparisonResult.push({
internalID: row2[internalIDCol] || null,
status: “add-action”,
data: row2
});
} else if (row2) {
let isChanged = false;
Object.keys(row1).forEach(key => {
if (row1[key] !== row2[key]) {
isChanged = true;
modifiedRow[key] = row2[key]; // Store new value
keyStatus[key] = “changed”; // Mark this key as changed
} else {
keyStatus[key] = “”; // No change for this key
}
});
if (isChanged) {
status = “changed”;
}
comparisonResult.push({
internalID,
status,
keyStatus, // Stores individual key status
data: modifiedRow
});
modifiedCsvMap.delete(internalID); // Remove matched row from modifiedCsvMap
}
});
// Process remaining rows in modifiedCsvMap (add-action for new records)
modifiedCsvMap.forEach(row2 => {
comparisonResult.push({
internalID: row2[internalIDCol] || null,
status: “add-action”,
data: row2
});
});
// console.log(‘Comparison Result Before:’, comparisonResult);
// Sort the array by Step Number
comparisonResult.sort((a, b) => {
const stepA = parseInt(a.data[“Step Number”], 10) || 0;
const stepB = parseInt(b.data[“Step Number”], 10) || 0;
return stepA – stepB;
});
comparedJson = comparisonResult;
console.log(‘Comparison Result After:’, comparisonResult);
let tableHTML = “<table><thead><tr>”;
const headers = Object.keys(comparisonResult[0].data);
tableHTML += `<thead><tr>`;
headers.forEach(header => {
tableHTML += `<th>${header}</th>`;
});
tableHTML += `</tr></thead>`;
// Generate table rows
tableHTML += `<tbody>`;
comparisonResult.forEach(item => {
// Add row class based on status
let rowClass = “”;
if (item.status === “add-action”) {
rowClass = “add-action td”;
} else if (item.status === “delete-action”) {
rowClass = “delete-action td”;
}
tableHTML += `<tr class=”${rowClass}“>`;
// Add cells for each header
headers.forEach(header => {
const value = item.data[header] || “”; // Use empty string if the key doesn’t exist
// console.log(‘HEADER:’,header);
// console.log(‘KEY Status:’,item.keyStatus);
let cellClass = “”;
let keyStatus=”;
if(!item.keyStatus ){
}else{
if(header===“Internal ID” || header===“Timestamp of Entry” || header===“Timestamp of Exit”){
}else{
keyStatus = item.keyStatus[header] || “”; // Get key-specific status (default to empty string if not found)
}
}
// Apply the class based on key-specific status
if (keyStatus === “changed”) {
cellClass = “changed”; // Mark as changed if keyStatus is “changed”
} else if (keyStatus === “”) {
cellClass = “”; // No change for this key
}
// Add the value and cell class to the table cell
tableHTML += `<td class=”${cellClass}“>${value}</td>`;
});
tableHTML += `</tr>`;
});
tableHTML += `</tbody></table>`;
return tableHTML;
} catch (e) {
console.error(‘Error in generateComparisonTable’, {message:e.message,stack:e.stack});
}
}