@qe-libs/refi-qda 0.0.0-stage → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - First release, from QE Studio's importer (`js/importers/refi-qda.js`):
6
+ `readProject`, `toTable`, `toCSV`, `writeCSV`, `QdaError`.
7
+ - Runs in the browser (its own `DOMParser`) as well as Node (pass one in);
8
+ fflate is bundled; CSV writing no longer needs papaparse.
9
+ - Coders: `coder: 'all'` (every coder agrees) and `coder: 'each'` (one row
10
+ per utterance per coder, with a Coder column) and `coder: 'columns'` (each
11
+ code once per coder, "Code [Coder A]") besides `'any'` and a single
12
+ coder; `codersOf(project)` lists the coders and their coded spans; the
13
+ report records the `coder` used.
package/README.md CHANGED
@@ -1,3 +1,84 @@
1
- # Temporary Holding Version
1
+ # @qe-libs/refi-qda
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Reads REFI-QDA projects (`.qdpx`, exported by NVivo, ATLAS.ti, MAXQDA,
4
+ QualCoder and others) into the table ENA works on: one row per utterance,
5
+ a 0/1 column per code. Runs in the browser and in Node; about 6 KB gzipped
6
+ (the unzip library, fflate, is bundled).
7
+
8
+ REFI-QDA is the open exchange standard from the Rotterdam Exchange Format
9
+ Initiative. A `.qdpx` is a zip of `project.qde` (XML: codebook, coders,
10
+ sources, coded spans) and the source texts.
11
+
12
+ ## Use
13
+
14
+ ```js
15
+ import { toCSV } from '@qe-libs/refi-qda';
16
+
17
+ const { csv, report, coders, textColumn } = toCSV(bytes, { coder: 'any' });
18
+ ```
19
+
20
+ - `bytes`: an `ArrayBuffer`, `Uint8Array` or `Buffer`.
21
+ - `coder`: whose coding becomes the code columns, for projects coded by
22
+ more than one person (see `codersOf`):
23
+
24
+ | `coder` | Rows | A line is 1 for a code when… |
25
+ |---|---|---|
26
+ | `'any'` (default) | one per utterance | any coder coded it |
27
+ | `'all'` | one per utterance | every coder coded it |
28
+ | `'each'` | one per utterance per coder, with a `Coder` column | that row's coder coded it |
29
+ | `'columns'` | one per utterance; each code once per coder, `Code [Coder A]` | that column's coder coded it |
30
+ | a coder's name or GUID | one per utterance | that coder coded it |
31
+
32
+ `'each'` and `'columns'` keep every coder's coding: `'each'` so coders can
33
+ be compared or combined in the model (Coder as a unit or a group),
34
+ `'columns'` so each coder's version of a code can be chosen or compared as
35
+ codes.
36
+ - In Node, pass an XML parser: `toCSV(bytes, { DOMParser: require('@xmldom/xmldom').DOMParser })`.
37
+ The browser's own `DOMParser` is used otherwise.
38
+
39
+ In a page without a bundler, `dist/refi-qda.umd.js` defines `window.RefiQda`.
40
+
41
+ | Function | Returns |
42
+ |---|---|
43
+ | `codersOf(project)` | `[{ guid, name, spans }]`: the coders who coded something, with their number of coded spans |
44
+ | `readProject(bytes, { DOMParser })` | The project: `codes`, `users`, `sources` (text, variables, coded spans), `skipped` |
45
+ | `toTable(project, { coder })` | `{ fields, rows, report }` |
46
+ | `toCSV(bytes, { coder, DOMParser })` | `{ csv, fields, rows, report, coders, textColumn }` |
47
+ | `writeCSV(fields, rows)` | CSV text |
48
+
49
+ Errors are `QdaError`s with a `code`: `not-zip`, `no-project`, `bad-xml`,
50
+ `no-coder`, `misaligned`, `no-sources`. Their messages are written for the
51
+ person importing.
52
+
53
+ ## The table
54
+
55
+ Columns: `Source`, `Line`, `UserName` (the speaker), `Coder` (with
56
+ `coder: 'each'`), the sources' variables,
57
+ `Text`, then one column per codable code in codebook order (a repeated name
58
+ gets its parent's: `Design talk / Cost`).
59
+
60
+ Rows are the transcript lines that look like `Speaker: text`; a transcript
61
+ without such lines gives one row per non-empty line. A line is 1 for a code
62
+ when a span coded with it (by the chosen coder) overlaps the line.
63
+
64
+ ## Positions
65
+
66
+ Coded spans are character positions in each source's text, and exporters
67
+ count them differently: code points, UTF-16 units, or with CRLF line ends as
68
+ two characters. Each source's spans are checked against the span text the
69
+ project records, the reading that matches is used, and `report.notes` says
70
+ when it isn't plain code points. When fewer than 90% match, the import stops
71
+ (`misaligned`) rather than placing codes on the wrong lines.
72
+
73
+ `report` also gives counts, the coders, skipped sources (audio, PDF, images)
74
+ and how many spans were checked.
75
+
76
+ ## Develop
77
+
78
+ ```bash
79
+ npm install
80
+ npm test # builds dist/, then runs tests/ (node --test)
81
+ ```
82
+
83
+ `tests/fixtures/` holds a small invented project and the CSV expected for
84
+ each coder; `make_sample.py` regenerates them.
@@ -0,0 +1,562 @@
1
+ var F = Uint8Array, Q = Uint16Array, $e = Int32Array, de = new F([
2
+ 0,
3
+ 0,
4
+ 0,
5
+ 0,
6
+ 0,
7
+ 0,
8
+ 0,
9
+ 0,
10
+ 1,
11
+ 1,
12
+ 1,
13
+ 1,
14
+ 2,
15
+ 2,
16
+ 2,
17
+ 2,
18
+ 3,
19
+ 3,
20
+ 3,
21
+ 3,
22
+ 4,
23
+ 4,
24
+ 4,
25
+ 4,
26
+ 5,
27
+ 5,
28
+ 5,
29
+ 5,
30
+ 0,
31
+ /* unused */
32
+ 0,
33
+ 0,
34
+ /* impossible */
35
+ 0
36
+ ]), we = new F([
37
+ 0,
38
+ 0,
39
+ 0,
40
+ 0,
41
+ 1,
42
+ 1,
43
+ 2,
44
+ 2,
45
+ 3,
46
+ 3,
47
+ 4,
48
+ 4,
49
+ 5,
50
+ 5,
51
+ 6,
52
+ 6,
53
+ 7,
54
+ 7,
55
+ 8,
56
+ 8,
57
+ 9,
58
+ 9,
59
+ 10,
60
+ 10,
61
+ 11,
62
+ 11,
63
+ 12,
64
+ 12,
65
+ 13,
66
+ 13,
67
+ /* unused */
68
+ 0,
69
+ 0
70
+ ]), Te = new F([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]), xe = function(e, r) {
71
+ for (var n = new Q(31), t = 0; t < 31; ++t)
72
+ n[t] = r += 1 << e[t - 1];
73
+ for (var o = new $e(n[30]), t = 1; t < 30; ++t)
74
+ for (var c = n[t]; c < n[t + 1]; ++c)
75
+ o[c] = c - n[t] << 5 | t;
76
+ return { b: n, r: o };
77
+ }, be = xe(de, 2), Ae = be.b, Ee = be.r;
78
+ Ae[28] = 258, Ee[258] = 28;
79
+ var Fe = xe(we, 0), Me = Fe.b, ae = new Q(32768);
80
+ for (var w = 0; w < 32768; ++w) {
81
+ var q = (w & 43690) >> 1 | (w & 21845) << 1;
82
+ q = (q & 52428) >> 2 | (q & 13107) << 2, q = (q & 61680) >> 4 | (q & 3855) << 4, ae[w] = ((q & 65280) >> 8 | (q & 255) << 8) >> 1;
83
+ }
84
+ var H = (function(e, r, n) {
85
+ for (var t = e.length, o = 0, c = new Q(r); o < t; ++o)
86
+ e[o] && ++c[e[o] - 1];
87
+ var a = new Q(r);
88
+ for (o = 1; o < r; ++o)
89
+ a[o] = a[o - 1] + c[o - 1] << 1;
90
+ var s;
91
+ if (n) {
92
+ s = new Q(1 << r);
93
+ var m = 15 - r;
94
+ for (o = 0; o < t; ++o)
95
+ if (e[o])
96
+ for (var v = o << 4 | e[o], d = r - e[o], i = a[e[o] - 1]++ << d, p = i | (1 << d) - 1; i <= p; ++i)
97
+ s[ae[i] >> m] = v;
98
+ } else
99
+ for (s = new Q(t), o = 0; o < t; ++o)
100
+ e[o] && (s[o] = ae[a[e[o] - 1]++] >> 15 - e[o]);
101
+ return s;
102
+ }), J = new F(288);
103
+ for (var w = 0; w < 144; ++w)
104
+ J[w] = 8;
105
+ for (var w = 144; w < 256; ++w)
106
+ J[w] = 9;
107
+ for (var w = 256; w < 280; ++w)
108
+ J[w] = 7;
109
+ for (var w = 280; w < 288; ++w)
110
+ J[w] = 8;
111
+ var Ce = new F(32);
112
+ for (var w = 0; w < 32; ++w)
113
+ Ce[w] = 5;
114
+ var Ue = /* @__PURE__ */ H(J, 9, 1), De = /* @__PURE__ */ H(Ce, 5, 1), ne = function(e) {
115
+ for (var r = e[0], n = 1; n < e.length; ++n)
116
+ e[n] > r && (r = e[n]);
117
+ return r;
118
+ }, N = function(e, r, n) {
119
+ var t = r / 8 | 0;
120
+ return (e[t] | e[t + 1] << 8) >> (r & 7) & n;
121
+ }, te = function(e, r) {
122
+ var n = r / 8 | 0;
123
+ return (e[n] | e[n + 1] << 8 | e[n + 2] << 16) >> (r & 7);
124
+ }, Ne = function(e) {
125
+ return (e + 7) / 8 | 0;
126
+ }, fe = function(e, r, n) {
127
+ return (r == null || r < 0) && (r = 0), (n == null || n > e.length) && (n = e.length), new F(e.subarray(r, n));
128
+ }, Oe = [
129
+ "unexpected EOF",
130
+ "invalid block type",
131
+ "invalid length/literal",
132
+ "invalid distance",
133
+ "stream finished",
134
+ "no stream handler",
135
+ ,
136
+ // determined by compression function
137
+ "no callback",
138
+ "invalid UTF-8 data",
139
+ "extra field too long",
140
+ "date not in range 1980-2099",
141
+ "filename too long",
142
+ "stream finishing",
143
+ "invalid zip data"
144
+ // determined by unknown compression method
145
+ ], E = function(e, r, n) {
146
+ var t = new Error(r || Oe[e]);
147
+ if (t.code = e, Error.captureStackTrace && Error.captureStackTrace(t, E), !n)
148
+ throw t;
149
+ return t;
150
+ }, Pe = function(e, r, n, t) {
151
+ var o = e.length, c = t ? t.length : 0;
152
+ if (!o || r.f && !r.l)
153
+ return n || new F(0);
154
+ var a = !n, s = a || r.i != 2, m = r.i;
155
+ a && (n = new F(o * 3));
156
+ var v = function(pe) {
157
+ var ve = n.length;
158
+ if (pe > ve) {
159
+ var ge = new F(Math.max(ve * 2, pe));
160
+ ge.set(n), n = ge;
161
+ }
162
+ }, d = r.f || 0, i = r.p || 0, p = r.b || 0, u = r.l, A = r.d, x = r.m, g = r.n, k = o * 8;
163
+ do {
164
+ if (!u) {
165
+ d = N(e, i, 1);
166
+ var f = N(e, i + 1, 3);
167
+ if (i += 3, f)
168
+ if (f == 1)
169
+ u = Ue, A = De, x = 9, g = 5;
170
+ else if (f == 2) {
171
+ var R = N(e, i, 31) + 257, z = N(e, i + 10, 15) + 4, W = R + N(e, i + 5, 31) + 1;
172
+ i += 14;
173
+ for (var B = new F(W), j = new F(19), y = 0; y < z; ++y)
174
+ j[Te[y]] = N(e, i + y * 3, 7);
175
+ i += z * 3;
176
+ for (var Y = ne(j), K = (1 << Y) - 1, l = H(j, Y, 1), y = 0; y < W; ) {
177
+ var C = l[N(e, i, K)];
178
+ i += C & 15;
179
+ var h = C >> 4;
180
+ if (h < 16)
181
+ B[y++] = h;
182
+ else {
183
+ var T = 0, b = 0;
184
+ for (h == 16 ? (b = 3 + N(e, i, 3), i += 2, T = B[y - 1]) : h == 17 ? (b = 3 + N(e, i, 7), i += 3) : h == 18 && (b = 11 + N(e, i, 127), i += 7); b--; )
185
+ B[y++] = T;
186
+ }
187
+ }
188
+ var M = B.subarray(0, R), O = B.subarray(R);
189
+ x = ne(M), g = ne(O), u = H(M, x, 1), A = H(O, g, 1);
190
+ } else
191
+ E(1);
192
+ else {
193
+ var h = Ne(i) + 4, S = e[h - 4] | e[h - 3] << 8, $ = h + S;
194
+ if ($ > o) {
195
+ m && E(0);
196
+ break;
197
+ }
198
+ s && v(p + S), n.set(e.subarray(h, $), p), r.b = p += S, r.p = i = $ * 8, r.f = d;
199
+ continue;
200
+ }
201
+ if (i > k) {
202
+ m && E(0);
203
+ break;
204
+ }
205
+ }
206
+ s && v(p + 131072);
207
+ for (var ke = (1 << x) - 1, ye = (1 << g) - 1, _ = i; ; _ = i) {
208
+ var T = u[te(e, i) & ke], L = T >> 4;
209
+ if (i += T & 15, i > k) {
210
+ m && E(0);
211
+ break;
212
+ }
213
+ if (T || E(2), L < 256)
214
+ n[p++] = L;
215
+ else if (L == 256) {
216
+ _ = i, u = null;
217
+ break;
218
+ } else {
219
+ var le = L - 254;
220
+ if (L > 264) {
221
+ var y = L - 257, X = de[y];
222
+ le = N(e, i, (1 << X) - 1) + Ae[y], i += X;
223
+ }
224
+ var ee = A[te(e, i) & ye], re = ee >> 4;
225
+ ee || E(3), i += ee & 15;
226
+ var O = Me[re];
227
+ if (re > 3) {
228
+ var X = we[re];
229
+ O += te(e, i) & (1 << X) - 1, i += X;
230
+ }
231
+ if (i > k) {
232
+ m && E(0);
233
+ break;
234
+ }
235
+ s && v(p + 131072);
236
+ var ue = p + le;
237
+ if (p < O) {
238
+ var he = c - O, Se = Math.min(O, ue);
239
+ for (he + p < 0 && E(3); p < Se; ++p)
240
+ n[p] = t[he + p];
241
+ }
242
+ for (; p < ue; ++p)
243
+ n[p] = n[p - O];
244
+ }
245
+ }
246
+ r.l = u, r.p = _, r.b = p, r.f = d, u && (d = 1, r.m = x, r.d = A, r.n = g);
247
+ } while (!d);
248
+ return p != n.length && a ? fe(n, 0, p) : n.subarray(0, p);
249
+ }, Ve = /* @__PURE__ */ new F(0), V = function(e, r) {
250
+ return e[r] | e[r + 1] << 8;
251
+ }, D = function(e, r) {
252
+ return (e[r] | e[r + 1] << 8 | e[r + 2] << 16 | e[r + 3] << 24) >>> 0;
253
+ }, oe = function(e, r) {
254
+ return D(e, r) + D(e, r + 4) * 4294967296;
255
+ };
256
+ function Re(e, r) {
257
+ return Pe(e, { i: 2 }, r && r.out, r && r.dictionary);
258
+ }
259
+ var se = typeof TextDecoder < "u" && /* @__PURE__ */ new TextDecoder(), Be = 0;
260
+ try {
261
+ se.decode(Ve, { stream: !0 }), Be = 1;
262
+ } catch {
263
+ }
264
+ var qe = function(e) {
265
+ for (var r = "", n = 0; ; ) {
266
+ var t = e[n++], o = (t > 127) + (t > 223) + (t > 239);
267
+ if (n + o > e.length)
268
+ return { s: r, r: fe(e, n - 1) };
269
+ o ? o == 3 ? (t = ((t & 15) << 18 | (e[n++] & 63) << 12 | (e[n++] & 63) << 6 | e[n++] & 63) - 65536, r += String.fromCharCode(55296 | t >> 10, 56320 | t & 1023)) : o & 1 ? r += String.fromCharCode((t & 31) << 6 | e[n++] & 63) : r += String.fromCharCode((t & 15) << 12 | (e[n++] & 63) << 6 | e[n++] & 63) : r += String.fromCharCode(t);
270
+ }
271
+ };
272
+ function ie(e, r) {
273
+ if (r) {
274
+ for (var n = "", t = 0; t < e.length; t += 16384)
275
+ n += String.fromCharCode.apply(null, e.subarray(t, t + 16384));
276
+ return n;
277
+ } else {
278
+ if (se)
279
+ return se.decode(e);
280
+ var o = qe(e), c = o.s, n = o.r;
281
+ return n.length && E(8), c;
282
+ }
283
+ }
284
+ var ze = function(e, r) {
285
+ return r + 30 + V(e, r + 26) + V(e, r + 28);
286
+ }, je = function(e, r, n) {
287
+ var t = V(e, r + 28), o = V(e, r + 30), c = ie(e.subarray(r + 46, r + 46 + t), !(V(e, r + 8) & 2048)), a = r + 46 + t, s = Ie(e, a, o, n, D(e, r + 20), D(e, r + 24), D(e, r + 42)), m = s[0], v = s[1], d = s[2];
288
+ return [V(e, r + 10), m, v, c, a + o + V(e, r + 32), d];
289
+ }, Ie = function(e, r, n, t, o, c, a) {
290
+ var s = o == 4294967295, m = c == 4294967295, v = a == 4294967295, d = r + n, i = s + m + v;
291
+ if (t && i) {
292
+ for (; r + 4 < d; r += 4 + V(e, r + 2))
293
+ if (V(e, r) == 1)
294
+ return [
295
+ s ? oe(e, r + 4 + 8 * m) : o,
296
+ m ? oe(e, r + 4) : c,
297
+ v ? oe(e, r + 4 + 8 * (m + s)) : a,
298
+ 1
299
+ ];
300
+ t < 2 && E(13);
301
+ }
302
+ return [o, c, a, 0];
303
+ };
304
+ function Le(e, r) {
305
+ for (var n = {}, t = e.length - 22; D(e, t) != 101010256; --t)
306
+ (!t || e.length - t > 65558) && E(13);
307
+ var o = V(e, t + 8);
308
+ if (!o)
309
+ return {};
310
+ var c = D(e, t + 16), a = D(e, t - 20) == 117853008;
311
+ if (a) {
312
+ var s = D(e, t - 12);
313
+ a = D(e, s) == 101075792, a && (o = D(e, s + 32), c = D(e, s + 48));
314
+ }
315
+ for (var m = 0; m < o; ++m) {
316
+ var v = je(e, c, a), d = v[0], i = v[1], p = v[2], u = v[3], A = v[4], x = v[5], g = ze(e, x);
317
+ c = A, d ? d == 8 ? n[u] = Re(e.subarray(g, g + i), { out: new F(p) }) : E(14, "unknown compression type " + d) : n[u] = fe(e, g, g + i);
318
+ }
319
+ return n;
320
+ }
321
+ class G extends Error {
322
+ constructor(r, n) {
323
+ super(r), this.name = "QdaError", this.code = n;
324
+ }
325
+ }
326
+ const U = (e, r) => e ? Array.from(e.childNodes).filter((n) => n.nodeType === 1 && (!r || n.localName === r)) : [], P = (e, r) => U(e, r)[0], Z = (e) => e && e.textContent || "";
327
+ function Qe(e, r) {
328
+ const n = [];
329
+ let t;
330
+ try {
331
+ t = new e({ onError: (c, a) => {
332
+ c !== "warning" && n.push(a);
333
+ } }).parseFromString(r, "text/xml");
334
+ } catch (c) {
335
+ n.push(c.message);
336
+ }
337
+ const o = t && t.getElementsByTagName("parsererror")[0];
338
+ return o && n.push(Z(o).trim().split(`
339
+ `)[0]), { root: n.length ? null : t && t.documentElement, error: n[0] };
340
+ }
341
+ function me(e, r) {
342
+ const n = r.toLowerCase(), t = Object.keys(e).find((o) => o.toLowerCase() === n || o.toLowerCase().endsWith("/" + n));
343
+ return t ? e[t] : null;
344
+ }
345
+ function Ge(e, { DOMParser: r = globalThis.DOMParser } = {}) {
346
+ if (!r) throw new Error("readProject needs a DOMParser: pass { DOMParser } (in Node, from @xmldom/xmldom).");
347
+ let n;
348
+ try {
349
+ n = Le(e instanceof Uint8Array ? e : new Uint8Array(e));
350
+ } catch {
351
+ throw new G("This file isn't a .qdpx project (it can't be opened as a zip archive).", "not-zip");
352
+ }
353
+ const t = me(n, "project.qde");
354
+ if (!t) throw new G("This .qdpx has no project.qde inside, so it isn't a REFI-QDA project.", "no-project");
355
+ const { root: o, error: c } = Qe(r, ie(t));
356
+ if (!o || o.localName !== "Project")
357
+ throw new G(`project.qde couldn't be read as a REFI-QDA project${c ? `: ${c}` : ""}.`, "bad-xml");
358
+ const a = new Map(U(P(o, "Users"), "User").map((u) => [u.getAttribute("guid"), u.getAttribute("name") || u.getAttribute("guid")])), s = [];
359
+ (function u(A, x) {
360
+ for (const g of U(A, "Code")) {
361
+ const k = g.getAttribute("name") || "(unnamed code)";
362
+ s.push({
363
+ guid: g.getAttribute("guid"),
364
+ name: k,
365
+ parent: x,
366
+ codable: g.getAttribute("isCodable") !== "false",
367
+ description: Z(P(g, "Description")).trim()
368
+ }), u(g, k);
369
+ }
370
+ })(P(P(o, "CodeBook"), "Codes"), null);
371
+ const m = new Map(U(P(o, "Variables"), "Variable").map((u) => [u.getAttribute("guid"), u.getAttribute("name")])), v = (u) => {
372
+ var g;
373
+ const A = (g = P(u, "VariableRef")) == null ? void 0 : g.getAttribute("targetGUID"), x = U(u).find((k) => k.localName !== "VariableRef");
374
+ return [m.get(A) || A, Z(x).trim()];
375
+ }, d = /* @__PURE__ */ new Map();
376
+ for (const u of U(P(o, "Cases"), "Case")) {
377
+ const A = U(u, "SourceRef");
378
+ if (u.getAttribute("name") || A.length !== 1) continue;
379
+ const x = A[0].getAttribute("targetGUID"), g = d.get(x) || {};
380
+ for (const k of U(u, "VariableValue")) {
381
+ const [f, h] = v(k);
382
+ g[f] = h;
383
+ }
384
+ d.set(x, g);
385
+ }
386
+ const i = [], p = [];
387
+ for (const u of U(P(o, "Sources"))) {
388
+ const A = u.getAttribute("name") || u.getAttribute("guid");
389
+ if (u.localName !== "TextSource") {
390
+ p.push(`${A} (${u.localName.replace(/Source$/, "").toLowerCase()})`);
391
+ continue;
392
+ }
393
+ let x = null;
394
+ const g = P(u, "PlainTextContent");
395
+ if (g) x = Z(g);
396
+ else {
397
+ const h = u.getAttribute("plainTextPath") || "", S = /^internal:\/\/(.+)$/.exec(h);
398
+ if (!S) {
399
+ p.push(`${A} (its text is stored outside the project: ${h || "no path"})`);
400
+ continue;
401
+ }
402
+ const $ = me(n, "Sources/" + S[1]);
403
+ if (!$) {
404
+ p.push(`${A} (Sources/${S[1]} is missing)`);
405
+ continue;
406
+ }
407
+ x = ie($);
408
+ }
409
+ x = x.replace(/^/, "");
410
+ const k = { ...d.get(u.getAttribute("guid")) || {} };
411
+ for (const h of U(u, "VariableValue")) {
412
+ const [S, $] = v(h);
413
+ k[S] = $;
414
+ }
415
+ const f = U(u, "PlainTextSelection").map((h) => ({
416
+ start: +h.getAttribute("startPosition"),
417
+ end: +h.getAttribute("endPosition"),
418
+ name: h.getAttribute("name"),
419
+ codings: U(h, "Coding").map((S) => {
420
+ var $;
421
+ return {
422
+ code: ($ = P(S, "CodeRef")) == null ? void 0 : $.getAttribute("targetGUID"),
423
+ user: S.getAttribute("creatingUser") || h.getAttribute("creatingUser")
424
+ };
425
+ })
426
+ }));
427
+ i.push({ guid: u.getAttribute("guid"), name: A, raw: x, vars: k, selections: f });
428
+ }
429
+ return { name: o.getAttribute("name") || "", users: a, codes: s, sources: i, skipped: p };
430
+ }
431
+ function We(e) {
432
+ const r = Array.from(e), n = [];
433
+ let t = 0;
434
+ for (let s = 0; s < r.length; s++)
435
+ n.push(t), !(r[s] === "\r" && r[s + 1] === `
436
+ `) && t++;
437
+ n.push(t);
438
+ const o = e.replace(/\r\n?/g, `
439
+ `), c = [];
440
+ let a = 0;
441
+ for (const s of o)
442
+ c.push(a), s.length === 2 && c.push(a), a++;
443
+ return c.push(a), {
444
+ text: o,
445
+ cps: Array.from(o),
446
+ ways: {
447
+ "code points": (s) => s,
448
+ "code points, CRLF as two": (s) => n[s],
449
+ "UTF-16 units": (s) => c[s]
450
+ }
451
+ };
452
+ }
453
+ const ce = (e) => String(e || "").replace(/\s+/g, " ").trim();
454
+ function Ke(e, r, n) {
455
+ let t = 0;
456
+ for (const o of r) {
457
+ const c = n(o.start), a = n(o.end);
458
+ if (c === void 0 || a === void 0 || c > a) continue;
459
+ const s = ce(e.slice(c, a).join("")), m = ce(o.name);
460
+ s && (s === m || m.length >= 20 && s.startsWith(m)) && t++;
461
+ }
462
+ return t;
463
+ }
464
+ const Xe = new RegExp("^\\s*(\\p{L}[^:\\n]{0,39}?)\\s*:\\s+(\\S.*)$", "u"), I = "(no coder)";
465
+ function He(e) {
466
+ const r = new Set(e.codes.filter((a) => a.codable).map((a) => a.guid)), n = /* @__PURE__ */ new Map();
467
+ for (const a of e.sources) for (const s of a.selections)
468
+ new Set(s.codings.filter((v) => r.has(v.code)).map((v) => v.user || I)).forEach((v) => n.set(v, (n.get(v) || 0) + 1));
469
+ const t = [...e.users].filter(([a]) => n.has(a)).map(([a, s]) => ({ guid: a, name: s, spans: n.get(a) })), o = [...n.keys()].filter((a) => a !== I && !e.users.has(a)).map((a) => ({ guid: a, name: a, spans: n.get(a) })), c = n.has(I) ? [{ guid: I, name: I, spans: n.get(I) }] : [];
470
+ return [...t, ...o, ...c];
471
+ }
472
+ function Je(e, { coder: r = "any" } = {}) {
473
+ const n = { sources: 0, rows: 0, codes: 0, coders: [...new Set(e.users.values())], skippedSources: e.skipped, notes: [] }, t = He(e);
474
+ n.coder = r;
475
+ let o = null;
476
+ if (!["any", "all", "each", "columns"].includes(r) && (o = [...e.users].filter(([f, h]) => h === r || f === r).map(([f]) => f), !o.length && t.some((f) => f.guid === r) && (o = [r]), !o.length))
477
+ throw new G(`There's no coder called “${r}” in this project.`, "no-coder");
478
+ const c = r === "each", a = r === "columns", s = e.codes.filter((f) => f.codable), m = {};
479
+ s.forEach((f) => {
480
+ m[f.name] = (m[f.name] || 0) + 1;
481
+ });
482
+ const v = s.map((f) => m[f.name] > 1 && f.parent ? `${f.parent} / ${f.name}` : f.name);
483
+ n.codes = v.length;
484
+ const d = [];
485
+ e.sources.forEach((f) => Object.keys(f.vars).forEach((h) => {
486
+ d.includes(h) || d.push(h);
487
+ }));
488
+ const i = ["Source", "Line", "UserName", ...c ? ["Coder"] : [], ...d, "Text"], p = a ? v.flatMap((f) => t.map((h) => `${f} [${h.name}]`)) : v.map((f) => i.includes(f) ? `${f} (code)` : f), u = [...i, ...p];
489
+ n.columns = p.length;
490
+ const A = new Map(s.map((f, h) => [f.guid, h]));
491
+ let x = 0, g = 0;
492
+ const k = [];
493
+ for (const f of e.sources) {
494
+ n.sources++;
495
+ const { text: h, cps: S, ways: $ } = We(f.raw), R = f.selections.filter((l) => ce(l.name));
496
+ let z = "code points";
497
+ if (R.length) {
498
+ let l = -1;
499
+ for (const [C, T] of Object.entries($)) {
500
+ const b = Ke(S, R, T);
501
+ b > l && (l = b, z = C);
502
+ }
503
+ if (x += R.length, g += l, l < R.length * 0.9)
504
+ throw new G(`The coded spans in “${f.name}” don't line up with its text (${l} of ${R.length} match), so the codes can't be placed on lines. The file may have been changed after it was coded.`, "misaligned");
505
+ z !== "code points" && n.notes.push(`“${f.name}”: positions counted as ${z}.`);
506
+ }
507
+ const W = $[z], B = [];
508
+ let j = 0;
509
+ for (const l of h.split(`
510
+ `)) {
511
+ const C = Array.from(l).length;
512
+ B.push({ text: l, start: j, end: j + C }), j += C + 1;
513
+ }
514
+ let y = B.map((l, C) => {
515
+ const T = Xe.exec(l.text);
516
+ return T && { ...l, i: C, speaker: T[1], said: T[2].trim() };
517
+ }).filter(Boolean);
518
+ y.length || (y = B.map((l, C) => l.text.trim() && { ...l, i: C, speaker: "", said: l.text.trim() }).filter(Boolean), n.notes.push(`“${f.name}” has no “Name: text” lines, so each non-empty line is a row with no speaker.`));
519
+ const Y = f.selections.map((l) => ({
520
+ a: W(l.start),
521
+ b: W(l.end),
522
+ codings: l.codings.filter((C) => A.has(C.code)).map((C) => [A.get(C.code), C.user || I])
523
+ })).filter((l) => l.codings.length && l.a !== void 0 && l.b !== void 0), K = d.map((l) => f.vars[l] ?? "");
524
+ for (const l of y) {
525
+ const C = v.map(() => /* @__PURE__ */ new Set());
526
+ for (const b of Y) b.a < l.end && b.b > l.start && b.codings.forEach(([M, O]) => C[M].add(O));
527
+ if (a) {
528
+ k.push([f.name, l.i + 1, l.speaker, ...K, l.said, ...C.flatMap((b) => t.map((M) => +b.has(M.guid)))]);
529
+ continue;
530
+ }
531
+ if (c) {
532
+ for (const b of t) k.push([f.name, l.i + 1, l.speaker, b.name, ...K, l.said, ...C.map((M) => +M.has(b.guid))]);
533
+ continue;
534
+ }
535
+ const T = r === "any" ? (b) => b.size > 0 : r === "all" ? (b) => t.length > 0 && t.every((M) => b.has(M.guid)) : (b) => o.some((M) => b.has(M));
536
+ k.push([f.name, l.i + 1, l.speaker, ...K, l.said, ...C.map((b) => +T(b))]);
537
+ }
538
+ }
539
+ if (n.rows = k.length, n.positions = x ? `${g} of ${x} coded spans checked against their text` : "no span text to check positions against", !n.sources) throw new G(`This project has no text sources to import${e.skipped.length ? ` (skipped: ${e.skipped.join("; ")})` : ""}.`, "no-sources");
540
+ return { fields: u, rows: k, report: n };
541
+ }
542
+ const Ye = (e) => {
543
+ const r = e == null ? "" : String(e);
544
+ return /[",\r\n]/.test(r) || r[0] === " " || r[r.length - 1] === " " ? `"${r.replace(/"/g, '""')}"` : r;
545
+ };
546
+ function Ze(e, r) {
547
+ return [e, ...r].map((n) => n.map(Ye).join(",")).join(`
548
+ `);
549
+ }
550
+ function _e(e, { coder: r = "any", DOMParser: n } = {}) {
551
+ const { fields: t, rows: o, report: c } = Je(Ge(e, { DOMParser: n }), { coder: r });
552
+ return { csv: Ze(t, o), fields: t, rows: o, report: c, coders: c.coders, textColumn: "Text" };
553
+ }
554
+ export {
555
+ G as QdaError,
556
+ He as codersOf,
557
+ Ge as readProject,
558
+ _e as toCSV,
559
+ Je as toTable,
560
+ Ze as writeCSV
561
+ };
562
+ //# sourceMappingURL=refi-qda.es.js.map