-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathxmlParser.mz.test.ts
More file actions
156 lines (142 loc) · 4.38 KB
/
xmlParser.mz.test.ts
File metadata and controls
156 lines (142 loc) · 4.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/* eslint-disable @typescript-eslint/switch-exhaustiveness-check */
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { recursiveResolve } from 'ml-spectra-processing';
import { decode } from 'uint8-base64';
import { expect, test } from 'vitest';
import { parse } from '../parse.ts';
const decoder = new TextDecoder();
test('decompress zip file', async () => {
const fileNamePath = join(__dirname, 'assets/small.xml');
const xmlData = readFileSync(fileNamePath);
// eslint-disable-next-line unicorn/consistent-function-scoping
const tagValueProcessor = async (bytes: Uint8Array, node: any) => {
if (node.tagName !== 'binary') return decoder.decode(bytes);
const ontologies = node.parent.children.cvParam.map(
(entry: any) => entry.attributes.accession,
);
return decodeBase64(bytes, { ontologies });
};
const result = parse(xmlData, {
tagValueProcessor,
}) as any;
await recursiveResolve(result);
const oneSpectrum =
result.indexedmzML.mzML.run.spectrumList.spectrum[0].binaryDataArrayList;
expect(Array.isArray(oneSpectrum.binaryDataArray[0].cvParam)).toBe(true);
expect(ArrayBuffer.isView(oneSpectrum.binaryDataArray[0].binary)).toBe(true);
expect(oneSpectrum).toMatchObject({
$count: 2,
binaryDataArray: [
{
$encodedLength: 311352,
},
{
$encodedLength: 114828,
},
],
});
});
export async function decodeBase64(
base64: Uint8Array,
options: { ontologies?: string[] } = {},
) {
let precision;
let compression = '';
let float = true;
const endian = 'little';
const { ontologies = [] } = options;
if (ontologies) {
if (ontologies.includes('MS:1000519')) {
precision = 32;
float = false;
}
if (ontologies.includes('MS:1000520')) precision = 16;
if (ontologies.includes('MS:1000521')) precision = 32;
if (ontologies.includes('MS:1000522')) {
float = false;
precision = 64;
}
if (ontologies.includes('MS:1000523')) precision = 64;
if (ontologies.includes('MS:1000574')) compression = 'zlib';
}
let uint8Array = decode(base64);
switch (compression.toLowerCase()) {
case 'zlib': {
const ds = new DecompressionStream('deflate');
const decompressedStream = new Blob([
uint8Array as Uint8Array<ArrayBuffer>,
])
.stream()
.pipeThrough(ds);
const decompressedArrayBuffer = await new Response(
decompressedStream,
).arrayBuffer();
uint8Array = new Uint8Array(decompressedArrayBuffer);
break;
}
case '':
case 'none':
break;
default:
throw new Error(`Unknown compression algorithm: ${compression}`);
}
switch (endian.toLowerCase()) {
case 'little':
break;
case 'network':
case 'big':
{
// we will invert in place the data
let step;
switch (precision) {
case 32:
step = 4;
break;
case 64:
step = 8;
break;
default:
throw new Error('Can not process bigendian file');
}
for (
let i = 0;
i < uint8Array.length - (uint8Array.length % step);
i += step
) {
for (let j = 0; j < step / 2; j++) {
const temp = uint8Array[i + j] as number;
uint8Array[i + j] = uint8Array[i + step - 1 - j] as number;
uint8Array[i + step - 1 - j] = temp;
}
}
}
break;
default:
throw new TypeError(`Attributes endian not correct: ${endian}`);
}
/*
We should take care that the length of the Uint8Array is correct but the buffer
may be a little bit bigger because when decoding base 64 it may end with = or ==
and we plan the size in the buffer.
*/
if (float) {
switch (precision) {
case 32:
return new Float32Array(uint8Array.buffer, 0, uint8Array.length / 4);
case 64:
return new Float64Array(uint8Array.buffer, 0, uint8Array.length / 8);
default:
throw new TypeError(`Incorrect precision: ${precision}`);
}
} else {
switch (precision) {
case 32:
return new Int32Array(uint8Array.buffer, 0, uint8Array.length / 4);
case 64:
return new BigInt64Array(uint8Array.buffer, 0, uint8Array.length / 8);
default:
throw new TypeError(`Incorrect precision: ${precision}`);
}
}
}