fix: perte de chevauchements entiers à l'import HTML noScribe

Les noms d'ancre ts_ dont le suffixe locuteur contient des caractères
hors \w (ex. "//S01" quand une annotation de chevauchement est tapée
juste avant une coupure de segment) étaient rejetés par TS_NAME_RE,
faisant disparaître silencieusement tout le texte de l'ancre.
This commit is contained in:
Axelle Abbadie 2026-07-09 23:41:41 +02:00
parent 9716e0f541
commit b74f36e3cc
8 changed files with 57 additions and 5 deletions

View file

@ -1,5 +1,10 @@
# Changelog
## [prod] v0.1.8 — 9 juillet 2026
### Corrections
- `NoScribeHtmlParser` — les noms d'ancre dont le suffixe contient des caractères hors `\w` (ex. `ts_95480_96512_//S01`, produit quand une annotation de chevauchement `//S01:` est tapée juste avant une coupure de segment noScribe) étaient rejetés par `TS_NAME_RE` (`\w*`), faisant disparaître silencieusement tout le texte de l'ancre. Des chevauchements entiers pouvaient ainsi disparaître à l'import HTML. Le suffixe est désormais accepté tel quel.
## [prod] v0.1.7 — 10 juin 2026
### Nouvelles fonctionnalités

View file

@ -2,6 +2,11 @@
> Previous entries in French: [CHANGELOG.fr.md](CHANGELOG.fr.md)
## [prod] v0.1.8 — 9 July 2026
### Fixes
- `NoScribeHtmlParser` — anchor names with a suffix containing non-word characters (e.g. `ts_95480_96512_//S01`, produced when an overlap annotation like `//S01:` is typed right before a noScribe segment break) were rejected by `TS_NAME_RE` (`\w*`), silently dropping the entire anchor's text. Whole overlapping-speech segments could disappear on HTML import. The suffix is now accepted as-is.
## [prod] v0.1.7 — 10 June 2026
### New features

View file

@ -36562,7 +36562,7 @@ var VttParser = class {
// src/parsers/NoScribeHtmlParser.ts
var PARA_RE = /<p(?:\s[^>]*)?>(?<content>.*?)<\/p>/gs;
var ANCHOR_RE = /<a\s+name="(ts_[^"]+)"[^>]*>(.*?)<\/a>/gs;
var TS_NAME_RE = /^ts_(\d+)_(\d+)_(\w*)$/;
var TS_NAME_RE = /^ts_(\d+)_(\d+)_(.*)$/;
var DISPLAY_TS_RE = /^\[\d{2}:\d{2}:\d{2}\]$/;
var SPEAKER_RE = /^(.+?)\s*:\s*/;
var ALL_TAGS_RE2 = /<[^>]+>/g;

View file

@ -1,7 +1,7 @@
{
"id": "pseudonymizer-tool",
"name": "Pseudonymizer Tool",
"version": "0.1.7",
"version": "0.1.8",
"minAppVersion": "1.13.0",
"description": "Pseudonymize and correct interactional transcripts (Jefferson, ICOR, SRT, CHAT/CHA). Designed for qualitative researchers in linguistics and conversation analysis.",
"author": "Axelle Abbadie",

View file

@ -1,6 +1,6 @@
{
"name": "pseudonymizer-tool",
"version": "0.1.7",
"version": "0.1.8",
"description": "Obsidian plugin for pseudonymizing and correcting interactional transcripts (Jefferson, ICOR, SRT, CHAT/CHA).",
"author": "Axelle Abbadie",
"homepage": "https://cv.hal.science/axelle-abbadie/",

View file

@ -16,7 +16,11 @@ import type { VttDocument, VttCue, VttWord } from './VttParser';
const PARA_RE = /<p(?:\s[^>]*)?>(?<content>.*?)<\/p>/gs;
const ANCHOR_RE = /<a\s+name="(ts_[^"]+)"[^>]*>(.*?)<\/a>/gs;
const TS_NAME_RE = /^ts_(\d+)_(\d+)_(\w*)$/;
// Le suffixe après START_END est censé être l'id de locuteur (S00, S01...), mais
// noScribe peut y injecter des caractères arbitraires si l'utilisateur a tapé une
// annotation de chevauchement (ex. "//S01:") juste avant une coupure de segment —
// accepter n'importe quel caractère pour ne pas perdre le segment (cf. bug import HTML).
const TS_NAME_RE = /^ts_(\d+)_(\d+)_(.*)$/;
// Timestamps d'affichage [HH:MM:SS] générés par noScribe pour l'interface — ignorer
const DISPLAY_TS_RE = /^\[\d{2}:\d{2}:\d{2}\]$/;
// Locuteur suivi de " :" en début de texte

View file

@ -102,3 +102,40 @@ describe('NoScribeHtmlParser — parse', () => {
expect(turn.text).not.toMatch(/\[\d{2}:\d{2}:\d{2}\]/);
});
});
// ---- Régression : ancres avec suffixe de locuteur non standard --------------
// Quand l'utilisatrice tape une annotation de chevauchement ("//S01: ...") juste
// avant une coupure de segment noScribe, le nom d'ancre généré peut contenir ce
// texte dans le suffixe (ex. "ts_95480_96512_//S01" au lieu de "ts_95480_96512_S01").
// Avant le correctif, TS_NAME_RE (`\w*`) rejetait ces ancres et le texte entier
// du segment disparaissait silencieusement.
const NOSCRIBE_HTML_OVERLAP = `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<html><head><meta name="qrichtext" content="1" />
<meta charset="UTF-8" />
<meta name="audio_source" content="/Corpus/entretien_02.m4a" />
</head><body>
<div class="WordSection1">
<p><a name="ts_90660_95140_S00" ><span style="color: #000000">Expérimentateur : qu'on m'a dit pendant très très longtemps qu'il fallait se brosser des dents.</span></a><a name="ts_95480_96512_//S01" ><span style="color: #000000"> Bon, maintenant, je le fais." //S01:</span></a><a name="ts_96512_105792_//S03" ><span style="color: #000000"> C'était par flemme la vérité mais si je me brossais les dents.</span></a><a name="ts_105792_107904_S00" ><span style="color: #000000">// Ça a été, vous y arrivez de mieux en mieux, quand même.</span></a></p>
</div>
</body></html>`;
describe('NoScribeHtmlParser — ancres avec suffixe non standard (chevauchements)', () => {
test('ne perd pas le texte des ancres dont le suffixe contient des caractères hors \\w', () => {
const doc = parser.parse(NOSCRIBE_HTML_OVERLAP);
expect(doc.cues.length).toBe(1);
const turn = doc.cues[0];
expect(turn.text).toContain('Bon, maintenant, je le fais');
expect(turn.text).toContain("C'était par flemme");
expect(turn.text).toContain('Ça a été, vous y arrivez');
});
test('conserve le timing correct malgré le suffixe non standard', () => {
const doc = parser.parse(NOSCRIBE_HTML_OVERLAP);
const turn = doc.cues[0];
expect(turn.startTime).toBe('00:01:30.660');
expect(turn.endTime).toBe('00:01:47.904');
});
});

View file

@ -12,5 +12,6 @@
"0.1.4": "1.7.2",
"0.1.5": "1.7.2",
"0.1.6": "1.7.2",
"0.1.7": "1.13.0"
"0.1.7": "1.13.0",
"0.1.8": "1.13.0"
}