diff --git a/docs/README.md b/docs/README.md index 3b9c1a5..958a43c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,16 +1,106 @@ # Matrix -## How to use -- Clone the project using ```git clone https://github.com/tskxz/matrix``` -- install packages using ```pip install -r requirements.txt``` or using these following commands -``` -python3 -m venv venv -source ./venv/bin/activate -pip install -r ./requirements.txt +Matrix is a web-based matrix calculator built with Flask. It provides a user-friendly interface to perform a wide range of matrix operations, from basic arithmetic to advanced applications like message encryption and decryption. + +## Features + +- **Basic Arithmetic:** Addition, subtraction, and multiplication of matrices. +- **Scalar Operations:** Multiply a matrix by a scalar value. +- **Advanced Calculations:** + - Matrix Transpose + - Determinant (for square matrices of any size via Laplace expansion) + - Matrix Inverse (using the adjugate method) +- **Cryptography:** + - Encrypt text messages using an invertible encoding matrix (a form of Hill Cipher). + - Decrypt messages using the inverse of the encoding matrix. +- **Dynamic UI:** The interface dynamically generates input fields based on user-specified matrix dimensions. +- **Data Export:** Export the results of operations (including the input matrices) to JSON, XML, or HTML formats. +- **Error Handling:** Validates matrix dimensions and mathematical conditions (e.g., non-singular matrices for inversion) and provides clear error messages. + +## Technology Stack + +- **Backend:** Python, Flask +- **Frontend:** HTML, CSS, JavaScript (Vanilla) +- **Math Engine:** Custom `Matrix` class with core logic in Python. +- **Testing:** Pytest +- **CI/CD:** GitHub Actions + +## Installation and Usage + +To get a local copy up and running, follow these steps. + +1. **Clone the repository:** + ```sh + git clone https://github.com/tskxz/matrix.git + cd matrix + ``` + +2. **Create and activate a virtual environment:** + - On macOS/Linux: + ```sh + python3 -m venv venv + source venv/bin/activate + ``` + - On Windows: + ```sh + python -m venv venv + .\venv\Scripts\activate + ``` + +3. **Install dependencies:** + ```sh + pip install -r requirements.txt + ``` + +4. **Run the application:** + ```sh + python app.py + ``` + +5. Open your browser and navigate to `http://127.0.0.1:5000`. + +## Running Tests + +The project includes a suite of unit tests to ensure the correctness of the matrix operations. To run the tests: + +```sh +python -m pytest tests/ -v -s ``` -- then proceed to run ```python app.py``` -## Run unit tests +## Project Structure + +The repository is organized as follows: + +``` +├── app.py # Main Flask application with all routes +├── core/ +│ └── matrix.py # Core Matrix class with all mathematical logic +├── static/ +│ ├── css/ # CSS stylesheets +│ └── js/ # Frontend JavaScript for each operation +├── templates/ # HTML templates for the web interface +├── tests/ +│ ├── test_matrix.py # Unit tests for the Matrix class +│ └── test_routes.py # Unit tests for the Flask routes +├── docs/ # System analysis, diagrams, and documentation +├── requirements.txt # Python dependencies +└── .github/ # CI workflows and PR templates ``` -python -m pytest tests/test_matrix.py -v -s -``` \ No newline at end of file + +## Cryptography Implementation + +The application uses matrix multiplication for encryption and decryption, a method similar to the Hill Cipher. + +1. **Character Mapping:** Each character in the message is converted to a number based on a predefined map (`A=1, B=2, ..., ' '=29`). +2. **Message Matrix:** The resulting sequence of numbers is organized into a matrix (`M`). The number of rows of this matrix matches the dimensions of the encoding matrix. +3. **Encryption:** The message matrix `M` is multiplied by a user-provided, invertible *encoding matrix* `A` to produce the *encrypted matrix* `N`. + - `N = A * M` +4. **Decryption:** To recover the original message, the encrypted matrix `N` is multiplied by the inverse of the encoding matrix, `A⁻¹`. + - `M = A⁻¹ * N` +5. **Conversion to Text:** The numbers in the resulting matrix `M` are converted back to characters to reveal the original message. + +An encoding matrix must be invertible (i.e., its determinant must be non-zero) for decryption to be possible. The application validates this condition before performing any encryption. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. diff --git a/static/js/matrix-builder.js b/static/js/matrix-builder.js index 4e90f97..5cb9879 100644 --- a/static/js/matrix-builder.js +++ b/static/js/matrix-builder.js @@ -4,6 +4,45 @@ function generateMatrixInput(rows, cols, containerId, label, gridId) { const section = document.createElement('div'); section.className = 'matrix-section'; section.innerHTML = `

${label}

`; + + const header = document.createElement('div'); + const importBtn = document.createElement('button'); + const fileInput = document.createElement('input'); + importBtn.textContent = 'Importar CSV'; + importBtn.type = 'button'; + fileInput.type = 'file'; + fileInput.accept = '.csv,text/csv'; + fileInput.style.display = 'none'; + fileInput.dataset.matrixId = gridId; + + importBtn.addEventListener('click', function() { + fileInput.click(); + }); + + fileInput.addEventListener('change', function(event) { + const file = event.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + + reader.onload = function(e) { + const csvText = e.target.result; + importMatrixFromCSV(csvText, gridId, rows, cols); + }; + + reader.onerror = function() { + showError('Erro ao ler o ficheiro CSV.'); + }; + + reader.readAsText(file); + + event.target.value = ''; + }); + + header.appendChild(importBtn); + header.appendChild(fileInput); + + section.appendChild(header); const grid = document.createElement('div'); grid.className = 'matrix-input-grid'; diff --git a/static/js/utils.js b/static/js/utils.js index b075911..4281f7e 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -84,4 +84,76 @@ function prettyHTML(matrix, title) { html += ''; return html; +} + +function importMatrixFromCSV(csvText, matrixId, expectedRows, expectedCols) { + try { + // Divide o CSV em linhas + const lines = csvText.trim().split('\n').filter(line => line.trim() !== ''); + + if (lines.length === 0) { + throw new Error('Arquivo CSV vazio.'); + } + + // Parse da matriz + const matrix = lines.map(line => + line.split(',').map(num => { + const val = num.trim(); + return val === '' ? 0 : parseFloat(val); + }) + ); + + // Verifica se todas as linhas têm o mesmo número de colunas + const cols = matrix[0].length; + if (!matrix.every(row => row.length === cols)) { + throw new Error('Todas as linhas devem ter o mesmo número de elementos.'); + } + + // Verifica se as dimensões correspondem às esperadas + if (expectedRows && expectedCols) { + if (matrix.length !== expectedRows || cols !== expectedCols) { + throw new Error(`Dimensões incorretas. Esperado: ${expectedRows}x${expectedCols}, Obtido: ${matrix.length}x${cols}`); + } + } + + // Preenche os inputs da matriz + for (let i = 0; i < matrix.length; i++) { + for (let j = 0; j < matrix[i].length; j++) { + const input = document.querySelector(`#${matrixId} [data-row="${i}"][data-col="${j}"]`); + if (input) { + input.value = matrix[i][j]; + input.dispatchEvent(new Event('input')); + } + } + } + + showSuccess(`Matriz ${matrixId === 'matrix-a' ? 'A' : 'B'} importada com sucesso!`); + + } catch (error) { + console.error('Erro ao importar CSV:', error); + showError('Erro ao importar CSV: ' + error.message); + } +} + +function showSuccess(message) { + hideError(); + const successDiv = document.createElement('div'); + successDiv.className = 'success-message'; + successDiv.textContent = message; + successDiv.style.margin = '0.5rem 0'; + successDiv.style.padding = '0.5rem'; + successDiv.style.backgroundColor = '#d4edda'; + successDiv.style.color = '#155724'; + successDiv.style.border = '1px solid #c3e6cb'; + successDiv.style.borderRadius = '0.25rem'; + successDiv.style.fontSize = '0.9rem'; + + setTimeout(() => { + if (successDiv.parentNode) { + successDiv.parentNode.removeChild(successDiv); + } + }, 3000); + + const form = document.getElementById('matrix-form'); + form.parentNode.insertBefore(successDiv, form.nextSibling); } \ No newline at end of file