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 the visualizer_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:

  1. The Uncaught SyntaxError: Unexpected end of input error appears consistently in the browser console (tested in Chrome and Edge).
  2. 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 the DOMContentLoaded listener, and the closing </script> tag.
  3. 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.
  4. Restarting the Django development server does not resolve the issue.
  5. Performing aggressive browser cache clearing and hard refreshes does not resolve the issue.
  6. Trying a different browser (Edge) also shows the exact same SyntaxError and truncation.
  7. 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.
  8. 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
Вернуться на верх