stegdoc 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/decode.js +3 -2
- package/src/index.js +1 -1
- package/src/lib/xlsx-handler.js +66 -47
package/package.json
CHANGED
package/src/commands/decode.js
CHANGED
|
@@ -198,7 +198,8 @@ async function decodeV5(inputFile, format, firstReadResult, options, spinner, qu
|
|
|
198
198
|
spinner.succeed && spinner.succeed(`Found all ${totalPartsFound} parts`);
|
|
199
199
|
|
|
200
200
|
for (let i = 0; i < allParts.length; i++) {
|
|
201
|
-
const
|
|
201
|
+
const pct = Math.round(((i + 1) / totalPartsFound) * 100);
|
|
202
|
+
const partSpinner = quiet ? spinner : ora(`Decoding part ${i + 1}/${totalPartsFound} (${pct}%)...`).start();
|
|
202
203
|
|
|
203
204
|
const partResult = await readFile(allParts[i].path, format);
|
|
204
205
|
|
|
@@ -228,7 +229,7 @@ async function decodeV5(inputFile, format, firstReadResult, options, spinner, qu
|
|
|
228
229
|
writeTarget.write(partPayload);
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
partSpinner.succeed && partSpinner.succeed(`Part ${i + 1} decoded`);
|
|
232
|
+
partSpinner.succeed && partSpinner.succeed(`Part ${i + 1}/${totalPartsFound} decoded`);
|
|
232
233
|
}
|
|
233
234
|
} else {
|
|
234
235
|
// Single file
|
package/src/index.js
CHANGED
package/src/lib/xlsx-handler.js
CHANGED
|
@@ -117,6 +117,7 @@ async function createXlsxPartV5(options) {
|
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* Read a v5 log-embed XLSX file and extract payload.
|
|
120
|
+
* Uses fast regex-based XML scanning instead of full DOM parsing for speed.
|
|
120
121
|
* @param {string} xlsxPath - Path to XLSX file
|
|
121
122
|
* @returns {object} { payloadBuffer, metadataJson, encryptionMeta, metadata }
|
|
122
123
|
*/
|
|
@@ -125,62 +126,67 @@ async function readXlsxV5(xlsxPath) {
|
|
|
125
126
|
throw new Error(`XLSX file not found: ${xlsxPath}`);
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
// Read file once and reuse the zip instance
|
|
130
|
+
const fileBuffer = fs.readFileSync(xlsxPath);
|
|
131
|
+
const zip = new AdmZip(fileBuffer);
|
|
132
|
+
|
|
133
|
+
// Parse shared strings (only if present — v5 files created with useSharedStrings:false may not have them)
|
|
134
|
+
let sharedStrings = null;
|
|
135
|
+
const ssEntry = zip.getEntry('xl/sharedStrings.xml');
|
|
136
|
+
if (ssEntry) {
|
|
137
|
+
sharedStrings = [];
|
|
138
|
+
const ssXml = ssEntry.getData().toString('utf8');
|
|
139
|
+
// Fast extract: match each <si><t>...</t></si> or <si><t ...>...</t></si>
|
|
140
|
+
const siRegex = /<si><t[^>]*>([^<]*)<\/t><\/si>/g;
|
|
141
|
+
let match;
|
|
142
|
+
while ((match = siRegex.exec(ssXml)) !== null) {
|
|
143
|
+
sharedStrings.push(match[1]);
|
|
144
|
+
}
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
//
|
|
137
|
-
const
|
|
138
|
-
if (!
|
|
147
|
+
// Extract sheet1.xml raw string
|
|
148
|
+
const sheetEntry = zip.getEntry('xl/worksheets/sheet1.xml');
|
|
149
|
+
if (!sheetEntry) {
|
|
139
150
|
throw new Error('Sheet not found in XLSX file.');
|
|
140
151
|
}
|
|
152
|
+
const sheetXml = sheetEntry.getData().toString('utf8');
|
|
141
153
|
|
|
142
|
-
//
|
|
154
|
+
// Fast row extraction using regex — much faster than full DOM parsing
|
|
143
155
|
const allRows = [];
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
value = extractTextContent(cell.is.t);
|
|
173
|
-
} else if (cellValue !== undefined) {
|
|
174
|
-
value = String(cellValue);
|
|
175
|
-
} else {
|
|
176
|
-
value = '';
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
rowValues[colIdx] = value;
|
|
156
|
+
const rowRegex = /<row [^>]*>(.*?)<\/row>/gs;
|
|
157
|
+
const cellRegex = /<c r="([A-Z]+)\d+"(?: t="([^"]*)")?[^>]*>(?:<v>([^<]*)<\/v>|<is><t[^>]*>([^<]*)<\/t><\/is>)?<\/c>/g;
|
|
158
|
+
|
|
159
|
+
let rowMatch;
|
|
160
|
+
while ((rowMatch = rowRegex.exec(sheetXml)) !== null) {
|
|
161
|
+
const rowXml = rowMatch[1];
|
|
162
|
+
const rowValues = [];
|
|
163
|
+
|
|
164
|
+
let cellMatch;
|
|
165
|
+
cellRegex.lastIndex = 0;
|
|
166
|
+
while ((cellMatch = cellRegex.exec(rowXml)) !== null) {
|
|
167
|
+
const colLetter = cellMatch[1];
|
|
168
|
+
const cellType = cellMatch[2] || '';
|
|
169
|
+
const vValue = cellMatch[3];
|
|
170
|
+
const inlineValue = cellMatch[4];
|
|
171
|
+
|
|
172
|
+
const colIdx = colLetterToIndex(colLetter);
|
|
173
|
+
|
|
174
|
+
let value;
|
|
175
|
+
if (cellType === 's' && vValue !== undefined && sharedStrings) {
|
|
176
|
+
const ssIndex = parseInt(vValue, 10);
|
|
177
|
+
value = ssIndex < sharedStrings.length ? sharedStrings[ssIndex] : '';
|
|
178
|
+
} else if (inlineValue !== undefined) {
|
|
179
|
+
value = decodeXmlEntities(inlineValue);
|
|
180
|
+
} else if (vValue !== undefined) {
|
|
181
|
+
value = decodeXmlEntities(vValue);
|
|
182
|
+
} else {
|
|
183
|
+
value = '';
|
|
180
184
|
}
|
|
181
185
|
|
|
182
|
-
|
|
186
|
+
rowValues[colIdx] = value;
|
|
183
187
|
}
|
|
188
|
+
|
|
189
|
+
allRows.push(rowValues);
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
// Skip the first row (column headers)
|
|
@@ -194,6 +200,19 @@ async function readXlsxV5(xlsxPath) {
|
|
|
194
200
|
return decodeLogLines(dataRows);
|
|
195
201
|
}
|
|
196
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Decode XML entities in a string value
|
|
205
|
+
*/
|
|
206
|
+
function decodeXmlEntities(str) {
|
|
207
|
+
if (!str || !str.includes('&')) return str;
|
|
208
|
+
return str
|
|
209
|
+
.replace(/&/g, '&')
|
|
210
|
+
.replace(/</g, '<')
|
|
211
|
+
.replace(/>/g, '>')
|
|
212
|
+
.replace(/'/g, "'")
|
|
213
|
+
.replace(/"/g, '"');
|
|
214
|
+
}
|
|
215
|
+
|
|
197
216
|
/**
|
|
198
217
|
* Convert column letter to 0-based index (A=0, B=1, ..., Z=25, AA=26)
|
|
199
218
|
*/
|