Skip to content

Commit

Permalink
Add vcfsplit
Browse files Browse the repository at this point in the history
  • Loading branch information
a-ctor committed Jul 14, 2021
1 parent b7ea66b commit 32ca2e8
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 0 deletions.
25 changes: 25 additions & 0 deletions crc32.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Adapted from https://stackoverflow.com/questions/18638900/javascript-crc32
class Crc32 {
constructor(){
let c;
this.crcTable = [];
for(let n =0; n < 256; n++){
c = n;
for(let k =0; k < 8; k++){
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
this.crcTable[n] = c;
}
}

calculate(data) {
const crcTable = this.crcTable;
let crc = 0 ^ (-1);

for (let i = 0; i < data.length; i++ ) {
crc = (crc >>> 8) ^ crcTable[(crc ^ data[i]) & 0xFF];
}

return (crc ^ (-1)) >>> 0;
}
}
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<p>hello c:</p>
<ul>
<li><a href="pi">pi</a></li>
<li><a href="vcfsplit">vcfsplit</a></li>
</ul>
</body>
</html>
17 changes: 17 additions & 0 deletions save.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Adapted from https://stackoverflow.com/questions/13405129/javascript-create-and-save-file
function saveAs(data, filename, type) {
const file = new Blob([data], {type: type});
const url = URL.createObjectURL(file);

const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);

a.click();

setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
119 changes: 119 additions & 0 deletions vcfsplit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vcf-split</title>
</head>
<body>
<h1>VCF Splitter</h1>
<p>Split a VCF file into multiple VCF files</p>
<p>Just drag and drop a file on the page or choose it via picker below.</p>
<input id="fileInput" type="file" value="here" accept=".vcf,text/vcard" multiple="false" />
<script src="crc32.js"></script>
<script src="save.js"></script>
<script src="zip.js"></script>
<script>
Uint8Array.prototype.indexOf = function(data, index) {
if (data.length === 0)
throw "Search array cannot be empty.";
if (index < 0)
return -1;

let j = 0;
for (let i = index || 0; i < this.length; i++) {
if (this[i] === data[j]) {
j++;
if (data.length == j)
return i - j + 1;
} else {
j = 0;
}
}

return -1;
}

const endMarker = [0x45, 0x4E, 0x44, 0x3A, 0x56, 0x43, 0x41, 0x52, 0x44];
const nameMarker = [0x0A, 0x4E, 0x3A];
const newlineMarker = [0x0A];

const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder();

async function parseFile(file) {
const bytes = new Uint8Array(await file.arrayBuffer());
const files = [];

const usedFileNames = new Set();
usedFileNames.add("");

let from = 0, to = 0;
while ((to = bytes.indexOf(endMarker, from)) !== -1) {
to += endMarker.length;

const fileData = bytes.slice(from, to);

// Try to determine the file name
let name = "";
const nameStart = fileData.indexOf(nameMarker);
if (nameStart !== -1) {
const nameEnd = fileData.indexOf(newlineMarker, nameStart + nameMarker.length);
if (nameEnd !== -1) {
const nameUnescaped = utf8Decoder.decode(fileData.slice(nameStart + nameMarker.length, nameEnd)).trim();
name = nameUnescaped.replace(/[^a-z0-9]/gi, "_");
name = name.replace(/_{2,}/g, "_");
name = name.replace(/_$/g, "");
}
}

// Make sure the filename is unique
if (usedFileNames.has(name)) {
let originalName = name;

let i = 1;
do {
i++;
name = originalName + i;
} while (usedFileNames.has(name));
}
usedFileNames.add(name);

name = name + ".vcf";
files.push({
name,
nameData: utf8Encoder.encode(name),
fileData
});

// Skip newline
if (bytes[to] === 0x0D)
to++;
if (bytes[to] === 0x0A)
to++;

from = to;
}

return files;
}

const fileInput = document.getElementById("fileInput");
fileInput.addEventListener("change", async ev => {
const files = fileInput.files;
if (files.length === 0)
return;

const parts = await parseFile(files[0]);
console.info(parts);

const zip = createZip(parts);

saveAs(zip, "results.zip", "application/zip");

fileInput.value = null;
})
</script>
</body>
</html>
128 changes: 128 additions & 0 deletions zip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
class BinaryWriter {

constructor(buffer, offset, length) {
this.data = new DataView(buffer, offset, length);
this.position = 0;
}

writeInt16(value, littleEndian) {
this.data.setInt16(this.position, value, littleEndian);
this.position += 2;
}

writeInt32(value, littleEndian) {
this.data.setInt32(this.position, value, littleEndian);
this.position += 4;
}

writeData(data) {
const window = new Uint8Array(this.data.buffer, this.data.byteOffset + this.position, data.length);
for (let i = 0; i < data.length; i++)
window[i] = data[i];
this.position += data.length;
}
}

const crc = new Crc32();

function getEntryRecordSize(entry) {
return 30 + entry.nameData.length + entry.fileData.length;
}

function writeEntryRecord(entry, writer) {
writer.writeInt32(0x504B0304); // Signature
writer.writeInt32(0x0A000000); // Version and Flags
writer.writeInt16(0x0000); // Compression type
writer.writeInt16(0x0000); // Time
writer.writeInt16(0x0000); // Date
writer.writeInt32(crc.calculate(entry.fileData), true); // CRC32
writer.writeInt32(entry.fileData.length, true); // Compressed file size
writer.writeInt32(entry.fileData.length, true); // Uncompressed file size
writer.writeInt16(entry.nameData.length, true); // File name length
writer.writeInt16(0); // Extra field length
writer.writeData(entry.nameData);
writer.writeData(entry.fileData);
}

function getCentralDirectoryEntrySize(entry) {
return 46 + entry.nameData.length;
}

function writeCentralDirectoryEntry(entry, writer) {
writer.writeInt32(0x504b0102); // Signature
writer.writeInt16(0x3F00); // Version made by
writer.writeInt16(0x1000); // Version to extract
writer.writeInt16(0x0000); // Flags
writer.writeInt16(0x0000); // Compression type
writer.writeInt16(0x0000); // Time
writer.writeInt16(0x0000); // Date
writer.writeInt32(crc.calculate(entry.fileData), true); // CRC32
writer.writeInt32(entry.fileData.length, true); // Compressed file size
writer.writeInt32(entry.fileData.length, true); // Uncompressed file size
writer.writeInt16(entry.nameData.length, true); // File name length
writer.writeInt16(0); // Extra field length
writer.writeInt16(0); // Field comment length
writer.writeInt16(0); // Disk number
writer.writeInt16(0); // Internal attributes
writer.writeInt32(32, true); // External attributes
writer.writeInt32(entry.dataPosition, true); // Data offset
writer.writeData(entry.nameData);
}

function getEndLocatorSize() {
return 22;
}

function writeEndLocator(writer, entryCount, directoryOffset, directorySize) {
writer.writeInt32(0x504b0506); // Signature
writer.writeInt16(0); // Disk number
writer.writeInt16(0); // Start disk number
writer.writeInt16(entryCount, true); // Entries on disk
writer.writeInt16(entryCount, true); // Entries in directory
writer.writeInt32(directorySize, true); // Directory size
writer.writeInt32(directoryOffset, true); // Directory offset
writer.writeInt16(0); // Comment length
}

function createZip(files) {
let totalSize = 0;
for (let file of files) {
totalSize += getEntryRecordSize(file);
totalSize += getCentralDirectoryEntrySize(file);
}
totalSize += getEndLocatorSize();

const result = new ArrayBuffer(totalSize);


let position = 0;

// Write the records
for (let file of files) {
const entryRecordSize = getEntryRecordSize(file);
file.dataPosition = position;

const writer = new BinaryWriter(result, position, entryRecordSize);
writeEntryRecord(file, writer);
position += entryRecordSize;
}
const centralDirectoryStart = position;

// Write the directory
for (let file of files) {
const centralDirectoryEntrySize = getCentralDirectoryEntrySize(file);

const writer = new BinaryWriter(result, position, centralDirectoryEntrySize);
writeCentralDirectoryEntry(file, writer);
position += centralDirectoryEntrySize;
}

// Write the endlocator
{
const writer = new BinaryWriter(result, position, getEndLocatorSize());
writeEndLocator(writer, files.length, centralDirectoryStart, position - centralDirectoryStart);
}

console.info(new Uint8Array(result));
return result;
}

0 comments on commit 32ca2e8

Please sign in to comment.