You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
14 KiB
230 lines
14 KiB
<template> <template>
|
|
<form> <form>
|
|
<input type="file" id="uploadInput" accept=".xlsx" @chang <input type="file" id="uploadInput" accept=".xlsx" @chang
|
|
<button type="submit" @click.prevent="parseUpload" :disab <button type="submit" @click.prevent="parseUpload" :disab
|
|
</form> </form>
|
|
</template> </template>
|
|
|
|
<script> <script>
|
|
|
|
import { read, utils } from "xlsx"; import { read, utils } from "xlsx";
|
|
import { store } from '../store.js' | import { store, clearStore } from '../store.js'
|
|
import { HeaderAssociator } from '../helpers.js' | import { UserHeader } from "@/userHeader";
|
|
> import { HeaderTemplate, associateHeaders } from "@/headerTem
|
|
>
|
|
|
|
function findFirstNonBlankRow(worksheet) { function findFirstNonBlankRow(worksheet) {
|
|
const range = utils.decode_range(worksheet['!ref']); const range = utils.decode_range(worksheet['!ref']);
|
|
// We'll check at most of 20 rows // We'll check at most of 20 rows
|
|
for (let row = range.s.r; row <= Math.min(range.e.r,20); ro for (let row = range.s.r; row <= Math.min(range.e.r,20); ro
|
|
var headerRow = true; var headerRow = true;
|
|
for (let col = 0; col < 20; col++) { for (let col = 0; col < 20; col++) {
|
|
const cell = worksheet[utils.encode_cell({ r: row, c: c const cell = worksheet[utils.encode_cell({ r: row, c: c
|
|
if (!cell || cell.v === '') { if (!cell || cell.v === '') {
|
|
headerRow = false; headerRow = false;
|
|
break; break;
|
|
} }
|
|
} }
|
|
if (headerRow) { if (headerRow) {
|
|
return row; return row;
|
|
} }
|
|
} }
|
|
return null; return null;
|
|
} }
|
|
|
|
|
|
export default { export default {
|
|
name: 'FileUpload', name: 'FileUpload',
|
|
data() { data() {
|
|
return { return {
|
|
// User uploaded file // User uploaded file
|
|
userFile: null, userFile: null,
|
|
// Header Template provided by server // Header Template provided by server
|
|
headerTemplateList: [], | headerTemplateList: null,
|
|
> userHeaders: [],
|
|
// Defines whether data row is checked agianst value re // Defines whether data row is checked agianst value re
|
|
validateData: true, | invalidData: [],
|
|
// Store data for access by other components // Store data for access by other components
|
|
store, store,
|
|
// Controls whether or not the user can submit a file // Controls whether or not the user can submit a file
|
|
// - When a file is selected, then disabled when submit // - When a file is selected, then disabled when submit
|
|
canSubmit: false, canSubmit: false,
|
|
} }
|
|
}, },
|
|
async mounted() { async mounted() {
|
|
// We want to get the lastest template header specificati // We want to get the lastest template header specificati
|
|
try { try {
|
|
const configRequestOptions = { const configRequestOptions = {
|
|
method: 'GET', method: 'GET',
|
|
redirect: 'follow', redirect: 'follow',
|
|
}; };
|
|
const resp = await fetch(process.env.VUE_APP_TEMPLATE_H const resp = await fetch(process.env.VUE_APP_TEMPLATE_H
|
|
let respJson = await resp.json(); let respJson = await resp.json();
|
|
console.log(respJson); | console.info("RespJ: ", respJson);
|
|
this.headerTemplateList = respJson["HeaderTemplate"]; <
|
|
//FIXME: This is only for testing purposes, REMOVE THIS //FIXME: This is only for testing purposes, REMOVE THIS
|
|
this.validateData = true;//respJson["ValidateData"]; this.validateData = true;//respJson["ValidateData"];
|
|
console.log(this.headerTemplateList); | this.headerTemplateList = respJson["HeaderTemplate"].ma
|
|
console.log(this.validateData); | return new HeaderTemplate (headerTemplate["Header"],h
|
|
> this.validateData, headerTemplate["ValueRegex"], he
|
|
> });
|
|
> // Sort this list to make association faster
|
|
> this.headerTemplateList.sort((a,b) => {
|
|
> const ha = a.header.toLowerCase();
|
|
> const hb = b.header.toLowerCase();
|
|
> if (ha < hb) return -1;
|
|
> if (ha > hb) return 1;
|
|
> return 0;
|
|
> });
|
|
> console.info("Recieved HeaderTemplateList: ", this.head
|
|
} catch (error) { } catch (error) {
|
|
//FIXME: emit error internal issues //FIXME: emit error internal issues
|
|
console.error(error); console.error(error);
|
|
} }
|
|
}, },
|
|
methods: { methods: {
|
|
|
|
fileSelected(event) { fileSelected(event) {
|
|
// Make sure the user can't still act on the old file // Make sure the user can't still act on the old file
|
|
this.$emit('parsed-upload', null); | this.$emit('parsed-upload', null, []);
|
|
// Clear old file data // Clear old file data
|
|
store.missingHeaders = []; | this.invalidData = [];
|
|
store.documentHeaders = []; | this.userHeaders = [];
|
|
> clearStore();
|
|
>
|
|
> // If the user had previously selected a file,
|
|
> // we need to clear the old header associations
|
|
> if (this.userFile !== null) {
|
|
> this.headerTemplateList.map(headerTemplate => {
|
|
> headerTemplate.userHeader = null;
|
|
> })
|
|
> }
|
|
>
|
|
|
|
this.userFile = event.target.files[0]; this.userFile = event.target.files[0];
|
|
this.canSubmit = true; this.canSubmit = true;
|
|
}, },
|
|
|
|
getHeaders(worksheet, sheetName, headerIndex) { getHeaders(worksheet, sheetName, headerIndex) {
|
|
// Store the found headers // Store the found headers
|
|
var combinedHeadersString = ""; <
|
|
// Use the range to make sure we don't go out of bounds // Use the range to make sure we don't go out of bounds
|
|
const range = utils.decode_range(worksheet['!ref']); const range = utils.decode_range(worksheet['!ref']);
|
|
for (let col = 0; col < range.e.c; col++){ for (let col = 0; col < range.e.c; col++){
|
|
let cellAddress = utils.encode_cell({c: col, r: heade let cellAddress = utils.encode_cell({c: col, r: heade
|
|
let cellObj = worksheet[cellAddress]; let cellObj = worksheet[cellAddress];
|
|
// n represents number type (which is probably the st // n represents number type (which is probably the st
|
|
if (cellObj === undefined || cellObj.t === 'n') { bre if (cellObj === undefined || cellObj.t === 'n') { bre
|
|
// Add the header to the combined headers string | let dataAddress = utils.encode_cell({c: col, r: heade
|
|
combinedHeadersString += cellObj.v; | let dataValue = worksheet[dataAddress] ? worksheet[da
|
|
let headerAssociator = new HeaderAssociator(cellObj.v |
|
|
if (!this.validateData) {headerAssociator.isValidData | // Record the user header
|
|
store.documentHeaders.push(headerAssociator); | let userHeader = new UserHeader(cellObj.v, sheetName,
|
|
> this.userHeaders.push(userHeader);
|
|
} }
|
|
return combinedHeadersString; <
|
|
}, },
|
|
|
|
async parseUpload() { async parseUpload() {
|
|
this.canSubmit = false; this.canSubmit = false;
|
|
const fileBuffer = await this.userFile.arrayBuffer(); const fileBuffer = await this.userFile.arrayBuffer();
|
|
var workbook = await read(fileBuffer, {type: 'array'}); var workbook = await read(fileBuffer, {type: 'array'});
|
|
|
|
if (workbook === null) { if (workbook === null) {
|
|
//FIXME: Emit bad spreadsheet to app //FIXME: Emit bad spreadsheet to app
|
|
console.error(`No workbook found! Could not parse fil console.error(`No workbook found! Could not parse fil
|
|
return; return;
|
|
} }
|
|
|
|
var validWorksheets = [] | let validWorksheets = false;
|
|
var combinedHeadersString = ''; <
|
|
// Go through each worksheet // Go through each worksheet
|
|
for (let sheetName of workbook.SheetNames) { for (let sheetName of workbook.SheetNames) {
|
|
// Open the actual worksheet // Open the actual worksheet
|
|
let worksheet = workbook.Sheets[sheetName]; let worksheet = workbook.Sheets[sheetName];
|
|
// Find the first non-blank row of 20 // Find the first non-blank row of 20
|
|
var headerIndex = findFirstNonBlankRow(worksheet); var headerIndex = findFirstNonBlankRow(worksheet);
|
|
if (headerIndex === null) { if (headerIndex === null) {
|
|
// Probably not a sheet we need to process // Probably not a sheet we need to process
|
|
console.warn(`${sheetName} | No header row found!`) console.warn(`${sheetName} | No header row found!`)
|
|
} }
|
|
// Add the header to the combined headers string | // getHeaders will created the UserHeader objects
|
|
// getHeaders will created the AssociatedHeaders obje | this.getHeaders(worksheet, sheetName, headerIndex);
|
|
combinedHeadersString += this.getHeaders(worksheet, s | validWorksheets = true;
|
|
// Note which worksheets are actually being processed <
|
|
validWorksheets.push(sheetName); <
|
|
} }
|
|
if (validWorksheets.length === 0) { | if (!validWorksheets) {
|
|
//FIXME: emit bad spreadsheet to App //FIXME: emit bad spreadsheet to App
|
|
console.error("No worksheets found!"); console.error("No worksheets found!");
|
|
return; return;
|
|
} }
|
|
|
|
// Check each template regex against the combined heade | // Sort the user headers to make association faster
|
|
for (const headerTemplate of this.headerTemplateList) { | this.userHeaders.sort((a,b) => {
|
|
// 'i' option means ignore case | const ha = a.label.toLowerCase();
|
|
let headerRegex = RegExp(headerTemplate["HeaderRegex" | const hb = b.label.toLowerCase();
|
|
let headerName = headerTemplate["Header"]; | if (ha < hb) return -1;
|
|
| if (ha > hb) return 1;
|
|
// Check to see if the template header is present in | return 0;
|
|
let searchResults = headerRegex.exec(combinedHeadersS | });
|
|
if (searchResults === null) { |
|
|
store.missingHeaders.push(headerName); | // console.log("User Headers:", this.userHeaders);
|
|
continue; | // console.log("Template Headers:", this.headerTemplate
|
|
} | let processedArrays = associateHeaders(this.userHeaders
|
|
let docHeader = searchResults[0]; |
|
|
> store.unassociatedUserHeaders = processedArrays[0];
|
|
> store.unassociatedTemplateHeaders = this.headerTemplate
|
|
> !headerTemplate.isAssociated();
|
|
> });
|
|
> this.invalidData = processedArrays[1];
|
|
> console.log("Invalid Data:", this.invalidData);
|
|
> console.log("Unassociated:", store.unAssociated);
|
|
>
|
|
> console.log("Updated Template Headers:", this.headerTem
|
|
|
|
// Find the associated header <
|
|
for (let headerAssociator of store.documentHeaders) { <
|
|
if (headerAssociator.isSet()) {continue} <
|
|
if (headerAssociator.documentHeader === docHeader) <
|
|
// Set the associated template header <
|
|
headerAssociator.swapTemplateHeader(headerName); <
|
|
<
|
|
if (this.validateData) { <
|
|
// If we want to validate the data, then check <
|
|
let isNullable = headerTemplate["Nullable"]; <
|
|
let headerAddress = utils.decode_cell(headerAss <
|
|
// Now we can get the value of the cell below t <
|
|
let valueCellAddress = utils.encode_cell(header <
|
|
// We need to know which worksheet this cell be <
|
|
let worksheet = workbook.Sheets[headerAssociato <
|
|
let valueObj = worksheet[valueCellAddress]; <
|
|
// Check if the cell is null, if so does it mat <
|
|
if (valueObj !== undefined ) { <
|
|
// Test the value against regex <
|
|
let valueRegex = RegExp(headerTemplate["Value <
|
|
let dataValue = valueObj.v.toString(); <
|
|
let isValid = valueRegex.test(dataValue); <
|
|
if (!isValid) { <
|
|
// record the invalid data and set isValidD <
|
|
headerAssociator.invalidData(dataValue); <
|
|
} <
|
|
} else { <
|
|
// isValid matches isNullable <
|
|
headerAssociator.isValidData = isNullable; <
|
|
} <
|
|
} <
|
|
// We found the header and processed it so stop l <
|
|
break; <
|
|
} <
|
|
} <
|
|
} <
|
|
//TODO: Remove logs <
|
|
console.log(`Document headers (${store.documentHeaders. <
|
|
console.log(`Missing headers (${store.missingHeaders.le <
|
|
// emit the uploaded file & parsed data up to parent // emit the uploaded file & parsed data up to parent
|
|
this.$emit('parsed-upload', this.userFile); | this.$emit('parsed-upload', this.userFile, this.invalid
|
|
} }
|
|
} }
|
|
} }
|
|
</script> </script> |