Uncaught SyntaxError: Unexpected end of input in Django Template (inline JS)
I'm developing a Django web application to visualize data from uploaded XML files. I'm encountering a persistent Uncaught SyntaxError: Unexpected end of input
in the browser console when loading the data visualization page. This error prevents the JavaScript from running and the chart (using Chart.js) from displaying.
Problem:
When I navigate to the /visualizer/data-visualizer/
page, the browser console shows Uncaught SyntaxError: Unexpected end of input
, pointing to a line number within the main HTML response (e.g., data-visualizer/:3149:80
). The chart area on the page remains blank.
Context:
- Django Version: 5.2
- Python Version: [Your Python version, e.g., 3.9, 3.10]
- Operating System: Windows
- Web Server: Django Development Server (
manage.py runserver
) - The application workflow is: Upload XML file -> Select data fields -> View visualization page (
/visualizer/data-visualizer/
). - The visualization page is rendered by the
VisualizerInterfaceView
using thevisualizer_interface.html
template. - The template includes Chart.js via a CDN and has an inline
<script>
block containing custom JavaScript logic to retrieve data from JSON embedded in the template and render the chart.
Observed Behavior and Debugging Steps Taken:
- The
Uncaught SyntaxError: Unexpected end of input
error appears consistently in the browser console (tested in Chrome and Edge). - Looking at the browser's "View Page Source" for the
/visualizer/data-visualizer/
page reveals that the inline JavaScript code within the<script>
block is being cut off/truncated at the very end. This missing code includes closing curly braces, parentheses, the});
for theDOMContentLoaded
listener, and the closing</script>
tag. - I have visually confirmed multiple times that the
visualizer_interface.html
file on my local disk contains the complete and correct JavaScript code, including all the necessary closing elements. I have opened the file in a separate editor instance to verify its content on disk. - Restarting the Django development server does not resolve the issue.
- Performing aggressive browser cache clearing and hard refreshes does not resolve the issue.
- Trying a different browser (Edge) also shows the exact same
SyntaxError
and truncation. - The Django development server terminal output shows a
GET /visualizer/data-visualizer/ HTTP/1.1" 200 ...
response, indicating the page is being served without explicit server-side errors at that stage. - A separate issue related to saving uploaded files to the
media/datasets
directory was debugged and resolved, confirming that basic file saving/writing works in the project now.
Relevant Code:
(Please include the full, correct code for your visualizer_interface.html
here. This is the code you confirmed is complete on your disk.)
{% extends 'base.html' %}
{% comment %} visualizer_interface.html {% endcomment %}
{% load custom_filters %} {# Make sure you have custom_filters.py with the get_item filter and load it here #}
{% load static %}
{% block title %}Data Visualizer{% endblock %}
{# Page-specific CSS, including basic table and chart container styles #}
{% block extra_css %}
{# You can uncomment the link below if you have a static css/visualizer_interface.css file #}
{# <link rel="stylesheet" href="{% static 'css/visualizer_interface.css' %}"> #}
<style>
/* Basic styling for the table */
table {
border-collapse: collapse;
width: 100%;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
/* Style for the chart container */
.chart-container {
position: relative; /* Needed for Chart.js responsiveness */
width: 80%; /* Adjust as needed */
margin: 20px auto; /* Center the chart */
height: 400px; /* Give the container a height */
}
/* Optional: Style for messages */
.messages {
list-style: none;
padding: 0;
margin: 10px 0;
}
.messages li {
padding: 10px;
margin-bottom: 5px;
border-radius: 4px;
}
.messages .success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.messages .error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.messages .info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
</style>
{% endblock %}
{% block content %}
<div class="container mt-4">
<h2>Data Visualizer</h2>
{# Display messages #}
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{# Section to display extracted data in a table #}
<h3>Extracted Data Preview:</h3>
{% if extracted_data %}
<table class="table table-bordered table-striped">
<thead>
<tr>
{# Display the extracted data field names as headers #}
{% for field in chart_data_fields %}
<th>{{ field }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{# Iterate through the extracted data entries (rows) #}
{% for row in extracted_data %}
<tr>
{# Iterate through the fields for each row #}
{% for field in chart_data_fields %}
{# Access data using the field name as the key #}
{# Use the get_item filter defined in custom_filters.py #}
<td>{{ row|get_item:field }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No data has been extracted yet. Please upload an XML file and select data fields.</p>
<p><a href="{% url 'visualizer:upload_dataset' %}">Upload New File</a></p>
{% endif %}
{# Charting Section #}
{% if extracted_data %} {# Only show chart section if data is available #}
<hr> {# Horizontal rule for separation #}
<h3>Chart Options:</h3>
<div class="mb-3"> {# Margin bottom #}
<label for="chartType" class="form-label">Chart Type:</label>
<select id="chartType" class="form-select d-inline-block w-auto"> {# w-auto for bootstrap #}
<option value="bar">Bar Chart (Amount by Type)</option>
<option value="line">Line Chart (Amount over Time - Requires Date Handling)</option>
<option value="pie">Pie Chart (Total Amount by Type)</option>
<option value="doughnut">Doughnut Chart (Total Amount by Type)</option>
{# Add other relevant chart types if needed #}
</select>
<label for="chartColor" class="form-label ms-3">Chart Color:</label> {# margin start #}
<input type="color" id="chartColorInput" value="#007bff" class="form-control-color d-inline-block"> {# Default blue color, form-control-color for bootstrap #}
</div>
<h3>Data Chart:</h3>
<div class="chart-container">
{# Canvas element where the chart will be rendered #}
<canvas id="myChart"></canvas>
</div>
{% endif %}
</div> {# closes <div class="container mt-4"> #}
{# Safely embed JSON data for JavaScript - Place these lines HERE within block content, outside the container div #}
{{ extracted_data|json_script:"extractedDataJson" }}
{{ chart_data_fields|json_script:"chartDataFieldsJson" }}
{% endblock %} {# End of block content #}
{% block extra_js %}
{# Include charting libraries here (e.js., Chart.js) #}
{# Make sure you have internet access or serve this file locally #}
<script src="[https://cdn.jsdelivr.net/npm/chart.js](https://cdn.jsdelivr.net/npm/chart.js)"></script>
{# THIS IS YOUR CUSTOM CHARTING JAVASCRIPT #}
<script>
// Wrap the entire custom JavaScript code inside a DOMContentLoaded listener
// This ensures the script runs only after the HTML elements are available in the DOM
document.addEventListener('DOMContentLoaded', function() {
// --- Get data passed from the Django view context using json_script ---
// These elements are created by the {{ ...|json_script }} template tags
// document.getElementById will find them after DOMContentLoaded
const extractedDataElement = document.getElementById('extractedDataJson');
const chartDataFieldsElement = document.getElementById('chartDataFieldsJson');
let extractedData = [];
let chartDataFields = [];
// Safely parse the JSON content if the elements are found
if (extractedDataElement && extractedDataElement.textContent) {
extractedData = JSON.parse(extractedDataElement.textContent);
} else {
console.error("Extracted data element not found or empty.");
// You might want to display an error message to the user on the page
}
if (chartDataFieldsElement && chartDataFieldsElement.textContent) {
chartDataFields = JSON.parse(chartDataFieldsElement.textContent);
} else {
console.error("Chart data fields element not found or empty.");
// You might want to display an error message to the user on the page
}
console.log("Extracted Data (JS):", extractedData); // Debug log in browser console
console.log("Chart Data Fields (JS):", chartDataFields); // Debug log in browser console
// --- Helper Function to Clean and Convert Amount String to a Number ---
function cleanAmount(amountString) {
if (typeof amountString === 'string') {
// Remove dollar signs, commas, and potentially handle parentheses for negative numbers
let cleaned = amountString.replace(/[$,]/g, '').trim();
// Handle parentheses often used for negative numbers in accounting
if (cleaned.startsWith('(') && cleaned.endsWith(')')) {
cleaned = '-' + cleaned.substring(1, cleaned.length - 1);
}
// Attempt conversion to float
const value = parseFloat(cleaned);
return isNaN(value) ? 0 : value; // Return 0 for invalid conversion
}
return 0; // Return 0 for non-string input
}
// --- Function to Extract Data for Charting ---
function extractChartData(data, fields, chartType) {
const chartLabels = []; // For categories (e.g., 'Transfer', 'Credit') - from Column_4
const chartValues = []; // For numerical values (e.g., Amount) - from Column_5
// Find the actual keys for Column_4 and Column_5 based on the order in chartDataFields
// We assume Column_4 is Type and Column_5 is Amount for this specific XML
// Use Array.find to get the exact key string if it exists
const col4Key = fields.find(field => field === 'Column_4');
const col5Key = fields.find(field => field === 'Column_5');
// Check if the required columns were found in the extracted fields
if (!col4Key || !col5Key) {
console.error("Required columns (Column_4, Column_5) not found in extracted fields.");
// Return an object indicating an error, which renderChart will check
return { labels: [], values: [], error: "Required columns (Column_4, Column_5) not found in data fields." };
}
data.forEach(row => {
// Check if the row is a valid object and contains the required keys
if (typeof row === 'object' && row !== null && row.hasOwnProperty(col4Key) && row.hasOwnProperty(col5Key)) {
const label = row[col4Key]; // Get value from Column_4
const valueString = row[col5Key]; // Get value from Column_5
// Only include rows where both label and value string are not null/undefined
if (label != null && valueString != null) {
const value = cleanAmount(valueString);
// Only include if the cleaned value is a valid number (not NaN)
if (!isNaN(value)) {
chartLabels.push(label);
chartValues.push(value);
} else {
console.warn(`Skipping row due to invalid amount in ${col5Key}:`, valueString);
}
} else {
console.warn(`Skipping row due to missing data in ${col4Key} or ${col5Key}:`, row);
}
} else {
console.warn("Skipping invalid row data structure:", row);
}
});
// --- Data Aggregation (Necessary for Pie/Doughnut, Optional for others) ---
const aggregatedData = {};
// Aggregate values by label (Type)
chartLabels.forEach((label, index) => {
if (!aggregatedData[label]) {
aggregatedData[label] = 0;
}
aggregatedData[label] += chartValues[index];
});
// Prepare data and labels for display based on chart type
let displayLabels = Object.keys(aggregatedData); // Always use aggregated labels for charts
let displayValues = Object.values(aggregatedData); // Always use aggregated values for charts
let chartTitle = 'Total Transaction Amount by Type'; // Default title for aggregated data
// Optional: If you want individual bars/points for Bar/Line, keep this logic:
// let displayLabels = chartLabels;
// let displayValues = chartValues;
// let chartTitle = 'Transaction Amounts by Type (Individual)';
// --- Return the processed data object ---
return {
labels: displayLabels,
values: displayValues,
aggregated: aggregatedData, // Include aggregated data for potential future use
chartTitle: chartTitle
};
} // <-- End of extractChartData function
// --- Function to Render the Chart ---
function renderChart() {
// Check if the canvas element exists before trying to get context
const canvasElement = document.getElementById('myChart');
if (!canvasElement) {
console.error("Canvas element with ID 'myChart' not found.");
return; // Stop if canvas is missing
}
// Destroy existing chart instance if it exists to prevent duplicates
if (window.myChartInstance) { // Use a property on the window object to store the chart instance globally
window.myChartInstance.destroy();
}
// Get selected chart type and color from controls
const chartTypeSelect = document.getElementById('chartType');
const chartColorInput = document.getElementById('chartColorInput'); // Corrected ID
if (!chartTypeSelect || !chartColorInput) {
console.error("Chart type or color controls not found.");
// You might want to display an error message to the user
return; // Stop if controls are missing
}
const selectedChartType = chartTypeSelect.value;
let selectedChartColor = chartColorInput.value; // Use let as it might be overridden for pie/doughnut
// Extract and process data based on the selected chart type
// Pass the *selected* chart type to extractChartData to influence aggregation
const chartData = extractChartData(extractedData, chartDataFields, selectedChartType);
// Check if extractChartData returned an error
if (chartData.error) {
// Display the error message on the canvas
const ctx = canvasElement.getContext('2d');
if(ctx) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Clear previous content
ctx.font = "16px Arial";
ctx.fillStyle = "red";
// Draw text, potentially wrapping or adjusting position for long messages
ctx.fillText("Error: " + chartData.error, 10, 50); // Display the error text on the canvas
}
console.error(chartData.error); // Log error to console
return; // Stop rendering if data extraction failed
}
// Get the 2D rendering context for the canvas
const ctx = canvasElement.getContext('2d');
if (!ctx) {
console.error("Failed to get 2D context for canvas.");
return; // Stop if context cannot be obtained
}
// --- Chart.js Configuration ---
const datasets = [{
label: chartData.chartTitle || 'Data', // Use title from extractChartData or default
data: chartData.values, // Numerical data values
borderWidth: 1
}];
// Set colors based on chart type
if (selectedChartType === 'pie' || selectedChartType === 'doughnut') {
// Generate a palette of colors for segments
// Use HSL for better control over distinct colors
const backgroundColors = chartData.labels.map((label, index) => `hsl(${(index * 360 / chartData.labels.length) % 360}, 70%, 60%)`); // Generate distinct colors
datasets[0].backgroundColor = backgroundColors;
// Create slightly darker border colors based on background colors
datasets[0].borderColor = backgroundColors.map(color => color.replace('60%)', '40%)'));
} else {
// Use the selected single color for bar/line charts
datasets[0].backgroundColor = selectedChartColor;
// Use the same color for the border (or calculate a darker shade if preferred)
datasets[0].borderColor = selectedChartColor;
// Optional: Configure scales if needed for bar/line
// scales: { y: { beginAtZero: true }, x: {} }
}
// Create the new Chart.js instance and store it
window.myChartInstance = new Chart(ctx, {
type: selectedChartType, // User selected type
data: {
labels: chartData.labels, // Labels (Type names or aggregated labels)
datasets: datasets // The dataset(s) containing the data and styling
},
options: {
responsive: true, // Chart resizes with its container
maintainAspectRatio: false, // Allows chart to fill the container height
scales: { // Define scales based on chart type
y: {
beginAtZero: true,
display: selectedChartType !== 'pie' && selectedChartType !== 'doughnut' // Hide y-axis for pie/doughnut
},
x: {
display: selectedChartType !== 'pie' && selectedChartType !== 'doughnut' // Hide x-axis for pie/doughnut
}
},
plugins: {
legend: {
display: selectedChartType === 'pie' || selectedChartType === 'doughnut' // Only show legend for pie/doughnut
},
title: {
display: true, // Show the chart title
text: chartData.chartTitle || 'Data Chart' // Use title from extractChartData or default
},
tooltip: { // Optional: Add tooltips for better interactivity
enabled: true
}
}
}
});
}
// --- Event Listeners and Initial Render ---
// Add event listeners to the chart controls to re-render the chart when they change
const chartTypeSelect = document.getElementById('chartType');
const chartColorInput = document.getElementById('chartColorInput'); // Corrected ID
// Check if controls were found before adding listeners
if(chartTypeSelect) {
chartTypeSelect.addEventListener('change', renderChart);
} else {
console.error("Chart type select element not found.");
}
if(chartColorInput) {
chartColorInput.addEventListener('input', renderChart); // Use 'input' for real-time color updates
} else {
console.error("Chart color input element not found.");
}
// Render the chart initially when the DOM is fully loaded
renderChart(); // Call renderChart directly now that DOMContentLoaded is complete
}); // <--- This is the correct closing of the DOMContentLoaded listener
</script> {# <-- This closes the script tag containing your custom JavaScript #}
{% endblock %} {# <-- This is the correct closing of the {% block extra_js %}. It should be the very last line of the file. #} terminal output PS C:\Users\Jason\OneDrive\Desktop\datavis_project> python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
April 19, 2025 - 19:23:27
Django version 5.2, using settings 'datavis_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead.
For more information on production servers see: https://docs.djangoproject.com/en/5.2/howto/deployment/
--- GET method in UploadDatasetView called ---
[19/Apr/2025 19:23:36] "GET /visualizer/ HTTP/1.1" 200 714
--- POST method in UploadDatasetView started ---
Form received. Is valid: True
Form is valid. Proceeding with file processing.
Uploaded file: Bank of America _ Online Banking _ Deposit _ Print Transaction Details.xml, Extension: xml
Handling XML file.
File saved to: C:\Users\Jason\OneDrive\Desktop\datavis_project\media\datasets\bank-of-america-_-online-banking-_-deposit-_-print-transaction-detailsxml
File content read from saved file and stored in session.
Calling analyze_xml_structure_recursive...
Analyzer: Root Element: {urn:schemas-microsoft-com:office:spreadsheet}Workbook
Analyzer: Namespaces:
: http://www.w3.org/TR/REC-html40
o: urn:schemas-microsoft-com:office:office
x: urn:schemas-microsoft-com:office:excel
ss: urn:schemas-microsoft-com:office:spreadsheet
html: http://www.w3.org/TR/REC-html40
Analyzer: Starting recursive element analysis...
Analyzer: Recursive analysis finished.
analyze_xml_structure_recursive finished.
Analyzer returned structure info. Storing in session.
XML structure and namespaces stored in session.
Redirecting to select_data_for_charting.
[19/Apr/2025 19:23:54] "POST /visualizer/ HTTP/1.1" 302 0
[19/Apr/2025 19:23:54] "GET /visualizer/select-data/ HTTP/1.1" 200 53767
[19/Apr/2025 19:23:54] "GET /static/css/style.css HTTP/1.1" 404 1853
--- POST method in SelectDataForChartingView called ---
User selected element tag: {urn:schemas-microsoft-com:office:spreadsheet}Row
User selected data field paths: ['{urn:schemas-microsoft-com:office:spreadsheet}Row__child__{urn:schemas-microsoft-com:office:spreadsheet}Cell', '{urn:schemas-microsoft-com:office:spreadsheet}Cell__child__{urn:schemas-microsoft-com:office:spreadsheet}Data']
XML content parsed in POST method for data extraction.
Found 122 elements matching the selected tag '{urn:schemas-microsoft-com:office:spreadsheet}Row'.
Identified parent of data tag: {urn:schemas-microsoft-com:office:spreadsheet}Cell
Identified data tag: {urn:schemas-microsoft-com:office:spreadsheet}Data
Extracted 122 data entries.
Identified Chart Data Fields: ['Column_1', 'Column_2', 'Column_3', 'Column_4', 'Column_5']
SelectDataForChartingView: Final chart_data_fields before saving to session: ['Column_1', 'Column_2', 'Column_3', 'Column_4', 'Column_5']
[19/Apr/2025 19:24:07] "POST /select-data/ HTTP/1.1" 302 0
--- GET method in VisualizerInterfaceView called ---
VisualizerInterfaceView: Request object: <WSGIRequest: GET '/visualizer/data-visualizer/'>
VisualizerInterfaceView: Request method: GET
VisualizerInterfaceView: Request path: /visualizer/data-visualizer/
VisualizerInterfaceView: Request GET: <QueryDict: {}>
VisualizerInterfaceView: Request POST: <QueryDict: {}>
VisualizerInterfaceView: Session keys: ['uploaded_dataset_name', 'extracted_chart_data', 'chart_data_fields', 'uploaded_file_content', '_xml_namespaces', '_xml_structure']
VisualizerInterfaceView: Retrieved 122 extracted data entries from session.
VisualizerInterfaceView: Retrieved Chart Data Fields from session: ['Column_1', 'Column_2', 'Column_3', 'Column_4', 'Column_5']
VisualizerInterfaceView: Rendering template with context keys: ['extracted_data', 'chart_data_fields']
[19/Apr/2025 19:24:07] "GET /visualizer/data-visualizer/ HTTP/1.1" 200 143476
[19/Apr/2025 19:24:07] "GET /static/css/style.css HTTP/1.1" 404 1853[enter image description here][1]
[1]: https://i.sstatic.net/fzzjiBp6.png