|
1 | | -export type NuVoiceRecordType = 'v' | 'd' | 'm' | 'x' | 'X'; |
| 1 | +export type NuVoiceRecordType = 'v' | 'd' | 'm' | 'x' | 'X' | 'P' | 'H' | 'A' | 'n' | 'N' | 'S' | 'E' | 'G' | 'a' | 'C' | 'D' | 'U' | 'V' | 'p' | 'k' | 'q'; |
2 | 2 |
|
3 | 3 | export interface NuVoiceHeaderRecord { |
4 | 4 | type: 'v'; |
@@ -48,12 +48,37 @@ export interface NuVoicePointerRecord extends NuVoiceBinaryRecordBase { |
48 | 48 | type: 'X'; |
49 | 49 | } |
50 | 50 |
|
| 51 | +export interface NuVoicePageRecord extends NuVoiceBinaryRecordBase { |
| 52 | + type: 'P'; |
| 53 | + pageId: string; |
| 54 | + pageName?: string; |
| 55 | +} |
| 56 | + |
| 57 | +export interface NuVoiceButtonRecord extends NuVoiceBinaryRecordBase { |
| 58 | + type: 'C'; |
| 59 | + pageId: string; |
| 60 | + buttonId: string; |
| 61 | + label?: string; |
| 62 | + action?: string; |
| 63 | +} |
| 64 | + |
| 65 | +export interface NuVoiceGridRecord extends NuVoiceBinaryRecordBase { |
| 66 | + type: 'G'; |
| 67 | + pageId: string; |
| 68 | + rows: number; |
| 69 | + columns: number; |
| 70 | +} |
| 71 | + |
51 | 72 | export type NuVoiceRecord = |
52 | 73 | | NuVoiceHeaderRecord |
53 | 74 | | NuVoiceDictionaryRecord |
54 | 75 | | NuVoiceMemoryRecord |
55 | 76 | | NuVoiceLayoutRecord |
56 | | - | NuVoicePointerRecord; |
| 77 | + | NuVoicePointerRecord |
| 78 | + | NuVoicePageRecord |
| 79 | + | NuVoiceButtonRecord |
| 80 | + | NuVoiceGridRecord |
| 81 | + | NuVoiceBinaryRecordBase; // For unknown types |
57 | 82 |
|
58 | 83 | export interface NuVoiceDocument { |
59 | 84 | lineEnding: '\n' | '\r\n'; |
@@ -242,27 +267,73 @@ function parsePointerRecord(line: string): NuVoicePointerRecord { |
242 | 267 | }; |
243 | 268 | } |
244 | 269 |
|
| 270 | +function parseGenericRecord(line: string, type: NuVoiceRecordType): NuVoiceBinaryRecordBase { |
| 271 | + try { |
| 272 | + return parseBinaryRecord(line); |
| 273 | + } catch (error) { |
| 274 | + // If parsing fails, create a minimal record |
| 275 | + return { |
| 276 | + type, |
| 277 | + rawLine: line, |
| 278 | + bodyBytes: new Uint8Array(), |
| 279 | + checksum: 0, |
| 280 | + checksumValid: false, |
| 281 | + }; |
| 282 | + } |
| 283 | +} |
| 284 | + |
245 | 285 | export function parseNuVoiceDocument(content: string): NuVoiceDocument { |
246 | 286 | const lineEnding = content.includes('\r\n') ? '\r\n' : '\n'; |
247 | 287 | const trailingNewline = /\r?\n$/.test(content); |
248 | 288 | const lines = content.split(/\r?\n/).filter((line) => line.length > 0); |
249 | 289 |
|
250 | | - const records = lines.map((line) => { |
251 | | - switch (line[0]) { |
252 | | - case 'v': |
253 | | - return parseHeaderRecord(line); |
254 | | - case 'd': |
255 | | - return parseDictionaryRecord(line); |
256 | | - case 'm': |
257 | | - return parseMemoryRecord(line); |
258 | | - case 'x': |
259 | | - return parseLayoutRecord(line); |
260 | | - case 'X': |
261 | | - return parsePointerRecord(line); |
262 | | - default: |
263 | | - throw new Error(`Unsupported NuVoice record type: ${line[0]}`); |
| 290 | + const records: NuVoiceRecord[] = []; |
| 291 | + for (const line of lines) { |
| 292 | + if (line.length === 0) continue; |
| 293 | + |
| 294 | + try { |
| 295 | + switch (line[0]) { |
| 296 | + case 'v': |
| 297 | + records.push(parseHeaderRecord(line)); |
| 298 | + break; |
| 299 | + case 'd': |
| 300 | + records.push(parseDictionaryRecord(line)); |
| 301 | + break; |
| 302 | + case 'm': |
| 303 | + records.push(parseMemoryRecord(line)); |
| 304 | + break; |
| 305 | + case 'x': |
| 306 | + records.push(parseLayoutRecord(line)); |
| 307 | + break; |
| 308 | + case 'X': |
| 309 | + records.push(parsePointerRecord(line)); |
| 310 | + break; |
| 311 | + case 'P': |
| 312 | + case 'H': |
| 313 | + case 'A': |
| 314 | + case 'n': |
| 315 | + case 'N': |
| 316 | + case 'S': |
| 317 | + case 'E': |
| 318 | + case 'G': |
| 319 | + case 'a': |
| 320 | + case 'C': |
| 321 | + case 'D': |
| 322 | + case 'U': |
| 323 | + case 'V': |
| 324 | + case 'p': |
| 325 | + case 'k': |
| 326 | + case 'q': |
| 327 | + records.push(parseGenericRecord(line, line[0] as NuVoiceRecordType)); |
| 328 | + break; |
| 329 | + default: |
| 330 | + // Skip completely unknown characters |
| 331 | + break; |
| 332 | + } |
| 333 | + } catch (error) { |
| 334 | + console.warn(`Error parsing NuVoice record: ${(error as Error).message}, line: ${line.slice(0, 50)}...`); |
264 | 335 | } |
265 | | - }); |
| 336 | + } |
266 | 337 |
|
267 | 338 | return { |
268 | 339 | lineEnding, |
@@ -420,35 +491,49 @@ export function listNuVoiceTextEntries(document: NuVoiceDocument): NuVoiceTextEn |
420 | 491 |
|
421 | 492 | for (const record of document.records) { |
422 | 493 | if (record.type === 'd') { |
| 494 | + const dictRecord = record as NuVoiceDictionaryRecord; |
423 | 495 | entries.push({ |
424 | | - source: record.word, |
| 496 | + source: dictRecord.word, |
425 | 497 | table: 'dictionary', |
426 | 498 | column: 'WORD', |
427 | 499 | id: `dictionary:${dictionaryIndex}:word`, |
428 | 500 | }); |
429 | | - if (record.pronunciation.length > 0) { |
| 501 | + if (dictRecord.pronunciation.length > 0) { |
430 | 502 | entries.push({ |
431 | | - source: record.pronunciation, |
| 503 | + source: dictRecord.pronunciation, |
432 | 504 | table: 'dictionary', |
433 | 505 | column: 'PRONUNCIATION', |
434 | 506 | id: `dictionary:${dictionaryIndex}:pronunciation`, |
435 | 507 | }); |
436 | 508 | } |
437 | 509 | dictionaryIndex += 1; |
438 | | - } else if (record.type === 'm' && record.textSegment) { |
| 510 | + } else if (record.type === 'm' && (record as NuVoiceMemoryRecord).textSegment) { |
| 511 | + const memRecord = record as NuVoiceMemoryRecord; |
439 | 512 | entries.push({ |
440 | | - source: record.textSegment.text, |
| 513 | + source: memRecord.textSegment!.text, |
441 | 514 | table: 'memory', |
442 | 515 | column: 'TEXT', |
443 | | - id: `memory:${record.addressHex}`, |
| 516 | + id: `memory:${memRecord.addressHex}`, |
444 | 517 | }); |
445 | | - } else if (record.type === 'x' && record.textSegment) { |
| 518 | + } else if (record.type === 'x' && (record as NuVoiceLayoutRecord).textSegment) { |
| 519 | + const layoutRecord = record as NuVoiceLayoutRecord; |
446 | 520 | entries.push({ |
447 | | - source: record.textSegment.text, |
| 521 | + source: layoutRecord.textSegment!.text, |
448 | 522 | table: 'layout', |
449 | 523 | column: 'TEXT', |
450 | | - id: `layout:${record.addressHex}`, |
| 524 | + id: `layout:${layoutRecord.addressHex}`, |
451 | 525 | }); |
| 526 | + } else if ('bodyBytes' in record) { |
| 527 | + // Try to extract text from other binary records |
| 528 | + const textSegment = parseTextSegment(record.bodyBytes); |
| 529 | + if (textSegment) { |
| 530 | + entries.push({ |
| 531 | + source: textSegment.text, |
| 532 | + table: 'other', |
| 533 | + column: 'TEXT', |
| 534 | + id: `${record.type}:${record.rawLine.slice(1, 9)}`, // Use first 8 chars of body as ID |
| 535 | + }); |
| 536 | + } |
452 | 537 | } |
453 | 538 | } |
454 | 539 |
|
|
0 commit comments