eyecite-ts 0.5.0 → 0.6.1

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.
Files changed (40) hide show
  1. package/README.md +38 -8
  2. package/dist/annotate/index.cjs.map +1 -1
  3. package/dist/annotate/index.d.cts +1 -1
  4. package/dist/annotate/index.d.mts +1 -1
  5. package/dist/annotate/index.mjs.map +1 -1
  6. package/dist/{citation-25ZydLsu.d.mts → citation-BB_vC_7x.d.cts} +215 -12
  7. package/dist/citation-BB_vC_7x.d.cts.map +1 -0
  8. package/dist/{citation-Cymq3pJ-.d.cts → citation-Bg1QDUYb.d.mts} +215 -12
  9. package/dist/citation-Bg1QDUYb.d.mts.map +1 -0
  10. package/dist/data/index.cjs.map +1 -1
  11. package/dist/data/index.d.cts +90 -90
  12. package/dist/data/index.d.cts.map +1 -1
  13. package/dist/data/index.d.mts +90 -90
  14. package/dist/data/index.d.mts.map +1 -1
  15. package/dist/data/index.mjs.map +1 -1
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +111 -128
  19. package/dist/index.d.cts.map +1 -1
  20. package/dist/index.d.mts +111 -128
  21. package/dist/index.d.mts.map +1 -1
  22. package/dist/index.mjs +1 -1
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/knownCodes-CI-vnoBO.cjs.map +1 -1
  25. package/dist/knownCodes-MkDSiR1j.mjs.map +1 -1
  26. package/dist/types-D0xwBESF.d.cts +115 -0
  27. package/dist/types-D0xwBESF.d.cts.map +1 -0
  28. package/dist/types-vAgBB1j3.d.mts +115 -0
  29. package/dist/types-vAgBB1j3.d.mts.map +1 -0
  30. package/dist/utils/index.cjs +7 -0
  31. package/dist/utils/index.cjs.map +1 -0
  32. package/dist/utils/index.d.cts +122 -0
  33. package/dist/utils/index.d.cts.map +1 -0
  34. package/dist/utils/index.d.mts +122 -0
  35. package/dist/utils/index.d.mts.map +1 -0
  36. package/dist/utils/index.mjs +7 -0
  37. package/dist/utils/index.mjs.map +1 -0
  38. package/package.json +10 -1
  39. package/dist/citation-25ZydLsu.d.mts.map +0 -1
  40. package/dist/citation-Cymq3pJ-.d.cts.map +0 -1
package/README.md CHANGED
@@ -15,7 +15,7 @@ Extract, resolve, and annotate legal citations from court opinions and legal doc
15
15
 
16
16
  ## Features
17
17
 
18
- - **Full citation extraction**: Case citations, statutes (20 jurisdictions), journal articles, neutral citations, public laws, federal register
18
+ - **Full citation extraction**: Case citations, statutes (20 jurisdictions), constitutional citations (U.S. + 50 states), journal articles, neutral citations, public laws, federal register
19
19
  - **Case name & full span**: Backward search extracts case names ("Smith v. Jones", "In re Smith"), `fullSpan` covers case name through closing parenthetical
20
20
  - **Parallel citation linking**: Automatic detection and grouping of comma-separated citations sharing a parenthetical (e.g., "410 U.S. 113, 93 S. Ct. 705 (1973)")
21
21
  - **Complex parentheticals**: Unified parser handles court+year, full dates (Jan. 15, 2020 / January 15, 2020 / 1/15/2020), disposition (en banc, per curiam), and chained parentheticals
@@ -115,6 +115,35 @@ const citations = extractCitations(text)
115
115
  | Abbreviated-code | FL, OH, MI, UT, CO, WA, NC, GA, PA, IN, NJ, DE |
116
116
  | Chapter-act | IL (ILCS) |
117
117
 
118
+ ### Constitutional Citations
119
+
120
+ Extract U.S. and state constitutional citations with article, amendment, section, and clause parsing:
121
+
122
+ ```typescript
123
+ import { extractCitations } from 'eyecite-ts'
124
+
125
+ const text = `
126
+ Under U.S. Const. amend. XIV, § 1, equal protection is guaranteed.
127
+ See also Cal. Const. art. I, § 7.
128
+ And U.S. Const. art. I, § 8, cl. 3.
129
+ `
130
+ const citations = extractCitations(text)
131
+
132
+ // U.S. amendment with section
133
+ // { type: 'constitutional', jurisdiction: 'US', amendment: 14,
134
+ // section: '1', confidence: 0.95 }
135
+
136
+ // California article with section
137
+ // { type: 'constitutional', jurisdiction: 'CA', article: 1,
138
+ // section: '7', confidence: 0.9 }
139
+
140
+ // Commerce Clause (article + section + clause)
141
+ // { type: 'constitutional', jurisdiction: 'US', article: 1,
142
+ // section: '8', clause: 3, confidence: 0.95 }
143
+ ```
144
+
145
+ Roman numerals (I–XXVII) are automatically parsed to integers. All 50 state abbreviations are supported.
146
+
118
147
  ### Async API
119
148
 
120
149
  ```typescript
@@ -410,9 +439,10 @@ All citation types use a discriminated union on the `type` field:
410
439
 
411
440
  ```typescript
412
441
  import type {
413
- Citation, // Union of all 9 types
442
+ Citation, // Union of all 11 types
414
443
  FullCaseCitation,
415
444
  StatuteCitation,
445
+ ConstitutionalCitation,
416
446
  JournalCitation,
417
447
  NeutralCitation,
418
448
  PublicLawCitation,
@@ -420,10 +450,10 @@ import type {
420
450
  IdCitation,
421
451
  SupraCitation,
422
452
  ShortFormCaseCitation,
423
- CitationOfType, // Extract subtype: CitationOfType<'case'> = FullCaseCitation
424
- ExtractorMap, // Maps FullCitationType keys to citation subtypes
425
- FullCitation, // Union of full citation types
426
- ShortFormCitation, // Union of short-form types
453
+ CitationOfType, // Extract subtype: CitationOfType<'case'> = FullCaseCitation
454
+ ExtractorMap, // Maps FullCitationType keys to citation subtypes
455
+ FullCitation, // Union of full citation types
456
+ ShortFormCitation, // Union of short-form types
427
457
  } from 'eyecite-ts'
428
458
  ```
429
459
 
@@ -452,7 +482,7 @@ if (isCitationType(citation, 'statute')) {
452
482
  switch (citation.type) {
453
483
  case 'case': /* ... */ break
454
484
  case 'statute': /* ... */ break
455
- // ... all 9 types ...
485
+ // ... all 11 types ...
456
486
  default: assertUnreachable(citation.type)
457
487
  }
458
488
  ```
@@ -509,7 +539,7 @@ pnpm lint # Lint with Biome
509
539
  pnpm format # Format with Biome
510
540
  ```
511
541
 
512
- 985+ tests across 32 test files.
542
+ 1030+ tests across 34 test files.
513
543
 
514
544
  ## License
515
545
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/annotate/annotate.ts"],"sourcesContent":["import type { Citation } from '../types/citation'\nimport type { AnnotationOptions, AnnotationResult } from './types'\n\n/**\n * Annotate citations in text with custom markup.\n *\n * Supports two modes:\n * - **Template mode**: Simple before/after wrapping (set `options.template`)\n * - **Callback mode**: Custom logic with full citation context (set `options.callback`)\n *\n * Citations are processed in reverse order to avoid position shifts invalidating\n * subsequent annotations. Position tracking maps original positions to new positions\n * after markup insertion.\n *\n * @param text - Original or cleaned text to annotate\n * @param citations - Citations to mark up (from extraction pipeline)\n * @param options - Annotation configuration\n * @returns Annotated text with position mapping\n *\n * @example Template mode\n * ```typescript\n * const result = annotate(text, citations, {\n * template: { before: '<cite>', after: '</cite>' }\n * })\n * // Result: \"See <cite>500 F.2d 123</cite>\"\n * ```\n *\n * @example Callback mode\n * ```typescript\n * const result = annotate(text, citations, {\n * callback: (citation) => {\n * if (citation.type === 'case') {\n * return `<a href=\"/cases/${citation.volume}\">${citation.matchedText}</a>`\n * }\n * return citation.matchedText\n * }\n * })\n * ```\n *\n * @example Position tracking\n * ```typescript\n * const result = annotate(text, citations, { template: { before: '<mark>', after: '</mark>' } })\n * // result.positionMap tracks how positions shifted\n * const originalPos = 10\n * const newPos = result.positionMap.get(originalPos)\n * ```\n */\nexport function annotate<C extends Citation = Citation>(\n text: string,\n citations: C[],\n options: AnnotationOptions<C> = {}\n): AnnotationResult {\n const {\n useCleanText = false,\n autoEscape = true, // Secure by default\n useFullSpan = false, // Backward compatible default\n template,\n callback,\n } = options\n\n // Sort reverse to avoid position shifts invalidating subsequent annotations\n const sorted = [...citations].sort((a, b) => {\n const aPos = useCleanText ? a.span.cleanStart : a.span.originalStart\n const bPos = useCleanText ? b.span.cleanStart : b.span.originalStart\n return bPos - aPos // Reverse for backward iteration\n })\n\n let result = text\n const positionMap = new Map<number, number>()\n const skipped: Citation[] = []\n\n for (const citation of sorted) {\n // Determine which span to use\n let start: number\n let end: number\n\n if (useFullSpan && 'fullSpan' in citation && citation.fullSpan) {\n // Full span mode: case name through parenthetical\n start = useCleanText ? citation.fullSpan.cleanStart : citation.fullSpan.originalStart\n end = useCleanText ? citation.fullSpan.cleanEnd : citation.fullSpan.originalEnd\n } else {\n // Default mode: core citation only\n start = useCleanText ? citation.span.cleanStart : citation.span.originalStart\n end = useCleanText ? citation.span.cleanEnd : citation.span.originalEnd\n }\n\n // Snap positions out of HTML tags when annotating original text\n if (!useCleanText) {\n const snapped = snapOutOfHtmlTags(result, start, end)\n if (snapped === null) {\n // Could not safely snap — skip this citation\n skipped.push(citation)\n continue\n }\n start = snapped.start\n end = snapped.end\n }\n\n let markup = ''\n\n if (callback) {\n // Callback mode: developer provides full logic\n const surrounding = text.substring(\n Math.max(0, start - 30),\n Math.min(text.length, end + 30)\n )\n markup = callback(citation, surrounding)\n } else if (template) {\n // Template mode: simple before/after wrapping\n const citationText = result.substring(start, end)\n const escaped = autoEscape ? escapeHtmlEntities(citationText) : citationText\n markup = template.before + escaped + template.after\n } else {\n // No annotation specified\n continue\n }\n\n // Insert annotation (working backwards preserves positions for later citations)\n result = result.slice(0, start) + markup + result.slice(end)\n\n // Track original position to new position (before this annotation was added)\n positionMap.set(start, start)\n }\n\n return { text: result, positionMap, skipped }\n}\n\n/**\n * Check if a position falls inside an HTML tag (between `<` and `>`).\n * Returns the index of the opening `<` if inside a tag, otherwise -1.\n */\nfunction findContainingTag(text: string, pos: number): { tagStart: number; tagEnd: number } | null {\n // Search backwards from pos for '<' without encountering '>' first\n let i = pos - 1\n while (i >= 0) {\n if (text[i] === '>') return null // Hit a tag close — we're outside\n if (text[i] === '<') {\n // Found opening '<' — now find the closing '>'\n let j = pos\n while (j < text.length) {\n if (text[j] === '>') return { tagStart: i, tagEnd: j + 1 }\n j++\n }\n // Unclosed tag — treat as inside\n return { tagStart: i, tagEnd: text.length }\n }\n i--\n }\n return null\n}\n\n/**\n * Snap annotation start/end positions to avoid landing inside HTML tags.\n *\n * If a position falls inside an HTML tag, it is moved:\n * - Start position: snapped to before the tag's `<`\n * - End position: snapped to after the tag's `>`\n *\n * Returns null if the positions can't be safely adjusted (e.g., entirely\n * within a single tag).\n */\nfunction snapOutOfHtmlTags(\n text: string,\n start: number,\n end: number,\n): { start: number; end: number } | null {\n let snappedStart = start\n let snappedEnd = end\n\n const startTag = findContainingTag(text, start)\n if (startTag) {\n snappedStart = startTag.tagStart\n }\n\n const endTag = findContainingTag(text, end)\n if (endTag) {\n snappedEnd = endTag.tagEnd\n }\n\n // Sanity check: start must come before end\n if (snappedStart >= snappedEnd) return null\n\n return { start: snappedStart, end: snappedEnd }\n}\n\n/**\n * Escape HTML entities to prevent XSS injection.\n *\n * Converts special HTML characters to their entity equivalents:\n * - `&` → `&amp;`\n * - `<` → `&lt;`\n * - `>` → `&gt;`\n * - `\"` → `&quot;`\n * - `'` → `&#39;`\n * - `/` → `&#x2F;`\n *\n * @param text - Text to escape\n * @returns Escaped text safe for HTML insertion\n */\nfunction escapeHtmlEntities(text: string): string {\n const map: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n }\n return text.replace(/[&<>\"'/]/g, (char) => map[char])\n}\n"],"mappings":"mEA+CA,SAAgB,EACd,EACA,EACA,EAAgC,EAAE,CAChB,CAClB,GAAM,CACJ,eAAe,GACf,aAAa,GACb,cAAc,GACd,WACA,YACE,EAGE,EAAS,CAAC,GAAG,EAAU,CAAC,MAAM,EAAG,IAAM,CAC3C,IAAM,EAAO,EAAe,EAAE,KAAK,WAAa,EAAE,KAAK,cAEvD,OADa,EAAe,EAAE,KAAK,WAAa,EAAE,KAAK,eACzC,GACd,CAEE,EAAS,EACP,EAAc,IAAI,IAClB,EAAsB,EAAE,CAE9B,IAAK,IAAM,KAAY,EAAQ,CAE7B,IAAI,EACA,EAaJ,GAXI,GAAe,aAAc,GAAY,EAAS,UAEpD,EAAQ,EAAe,EAAS,SAAS,WAAa,EAAS,SAAS,cACxE,EAAM,EAAe,EAAS,SAAS,SAAW,EAAS,SAAS,cAGpE,EAAQ,EAAe,EAAS,KAAK,WAAa,EAAS,KAAK,cAChE,EAAM,EAAe,EAAS,KAAK,SAAW,EAAS,KAAK,aAI1D,CAAC,EAAc,CACjB,IAAM,EAAU,EAAkB,EAAQ,EAAO,EAAI,CACrD,GAAI,IAAY,KAAM,CAEpB,EAAQ,KAAK,EAAS,CACtB,SAEF,EAAQ,EAAQ,MAChB,EAAM,EAAQ,IAGhB,IAAI,EAAS,GAEb,GAAI,EAMF,EAAS,EAAS,EAJE,EAAK,UACvB,KAAK,IAAI,EAAG,EAAQ,GAAG,CACvB,KAAK,IAAI,EAAK,OAAQ,EAAM,GAAG,CAChC,CACuC,SAC/B,EAAU,CAEnB,IAAM,EAAe,EAAO,UAAU,EAAO,EAAI,CAC3C,EAAU,EAAa,EAAmB,EAAa,CAAG,EAChE,EAAS,EAAS,OAAS,EAAU,EAAS,WAG9C,SAIF,EAAS,EAAO,MAAM,EAAG,EAAM,CAAG,EAAS,EAAO,MAAM,EAAI,CAG5D,EAAY,IAAI,EAAO,EAAM,CAG/B,MAAO,CAAE,KAAM,EAAQ,cAAa,UAAS,CAO/C,SAAS,EAAkB,EAAc,EAA0D,CAEjG,IAAI,EAAI,EAAM,EACd,KAAO,GAAK,GAAG,CACb,GAAI,EAAK,KAAO,IAAK,OAAO,KAC5B,GAAI,EAAK,KAAO,IAAK,CAEnB,IAAI,EAAI,EACR,KAAO,EAAI,EAAK,QAAQ,CACtB,GAAI,EAAK,KAAO,IAAK,MAAO,CAAE,SAAU,EAAG,OAAQ,EAAI,EAAG,CAC1D,IAGF,MAAO,CAAE,SAAU,EAAG,OAAQ,EAAK,OAAQ,CAE7C,IAEF,OAAO,KAaT,SAAS,EACP,EACA,EACA,EACuC,CACvC,IAAI,EAAe,EACf,EAAa,EAEX,EAAW,EAAkB,EAAM,EAAM,CAC3C,IACF,EAAe,EAAS,UAG1B,IAAM,EAAS,EAAkB,EAAM,EAAI,CAQ3C,OAPI,IACF,EAAa,EAAO,QAIlB,GAAgB,EAAmB,KAEhC,CAAE,MAAO,EAAc,IAAK,EAAY,CAiBjD,SAAS,EAAmB,EAAsB,CAChD,IAAM,EAA8B,CAClC,IAAK,QACL,IAAK,OACL,IAAK,OACL,IAAK,SACL,IAAK,QACL,IAAK,SACN,CACD,OAAO,EAAK,QAAQ,YAAc,GAAS,EAAI,GAAM"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/annotate/annotate.ts"],"sourcesContent":["import type { Citation } from \"../types/citation\"\nimport type { AnnotationOptions, AnnotationResult } from \"./types\"\n\n/**\n * Annotate citations in text with custom markup.\n *\n * Supports two modes:\n * - **Template mode**: Simple before/after wrapping (set `options.template`)\n * - **Callback mode**: Custom logic with full citation context (set `options.callback`)\n *\n * Citations are processed in reverse order to avoid position shifts invalidating\n * subsequent annotations. Position tracking maps original positions to new positions\n * after markup insertion.\n *\n * @param text - Original or cleaned text to annotate\n * @param citations - Citations to mark up (from extraction pipeline)\n * @param options - Annotation configuration\n * @returns Annotated text with position mapping\n *\n * @example Template mode\n * ```typescript\n * const result = annotate(text, citations, {\n * template: { before: '<cite>', after: '</cite>' }\n * })\n * // Result: \"See <cite>500 F.2d 123</cite>\"\n * ```\n *\n * @example Callback mode\n * ```typescript\n * const result = annotate(text, citations, {\n * callback: (citation) => {\n * if (citation.type === 'case') {\n * return `<a href=\"/cases/${citation.volume}\">${citation.matchedText}</a>`\n * }\n * return citation.matchedText\n * }\n * })\n * ```\n *\n * @example Position tracking\n * ```typescript\n * const result = annotate(text, citations, { template: { before: '<mark>', after: '</mark>' } })\n * // result.positionMap tracks how positions shifted\n * const originalPos = 10\n * const newPos = result.positionMap.get(originalPos)\n * ```\n */\nexport function annotate<C extends Citation = Citation>(\n text: string,\n citations: C[],\n options: AnnotationOptions<C> = {},\n): AnnotationResult {\n const {\n useCleanText = false,\n autoEscape = true, // Secure by default\n useFullSpan = false, // Backward compatible default\n template,\n callback,\n } = options\n\n // Sort reverse to avoid position shifts invalidating subsequent annotations\n const sorted = [...citations].sort((a, b) => {\n const aPos = useCleanText ? a.span.cleanStart : a.span.originalStart\n const bPos = useCleanText ? b.span.cleanStart : b.span.originalStart\n return bPos - aPos // Reverse for backward iteration\n })\n\n let result = text\n const positionMap = new Map<number, number>()\n const skipped: Citation[] = []\n\n for (const citation of sorted) {\n // Determine which span to use\n let start: number\n let end: number\n\n if (useFullSpan && \"fullSpan\" in citation && citation.fullSpan) {\n // Full span mode: case name through parenthetical\n start = useCleanText ? citation.fullSpan.cleanStart : citation.fullSpan.originalStart\n end = useCleanText ? citation.fullSpan.cleanEnd : citation.fullSpan.originalEnd\n } else {\n // Default mode: core citation only\n start = useCleanText ? citation.span.cleanStart : citation.span.originalStart\n end = useCleanText ? citation.span.cleanEnd : citation.span.originalEnd\n }\n\n // Snap positions out of HTML tags when annotating original text\n if (!useCleanText) {\n const snapped = snapOutOfHtmlTags(result, start, end)\n if (snapped === null) {\n // Could not safely snap — skip this citation\n skipped.push(citation)\n continue\n }\n start = snapped.start\n end = snapped.end\n }\n\n let markup = \"\"\n\n if (callback) {\n // Callback mode: developer provides full logic\n const surrounding = text.substring(Math.max(0, start - 30), Math.min(text.length, end + 30))\n markup = callback(citation, surrounding)\n } else if (template) {\n // Template mode: simple before/after wrapping\n const citationText = result.substring(start, end)\n const escaped = autoEscape ? escapeHtmlEntities(citationText) : citationText\n markup = template.before + escaped + template.after\n } else {\n // No annotation specified\n continue\n }\n\n // Insert annotation (working backwards preserves positions for later citations)\n result = result.slice(0, start) + markup + result.slice(end)\n\n // Track original position to new position (before this annotation was added)\n positionMap.set(start, start)\n }\n\n return { text: result, positionMap, skipped }\n}\n\n/**\n * Check if a position falls inside an HTML tag (between `<` and `>`).\n * Returns the index of the opening `<` if inside a tag, otherwise -1.\n */\nfunction findContainingTag(text: string, pos: number): { tagStart: number; tagEnd: number } | null {\n // Search backwards from pos for '<' without encountering '>' first\n let i = pos - 1\n while (i >= 0) {\n if (text[i] === \">\") return null // Hit a tag close — we're outside\n if (text[i] === \"<\") {\n // Found opening '<' — now find the closing '>'\n let j = pos\n while (j < text.length) {\n if (text[j] === \">\") return { tagStart: i, tagEnd: j + 1 }\n j++\n }\n // Unclosed tag — treat as inside\n return { tagStart: i, tagEnd: text.length }\n }\n i--\n }\n return null\n}\n\n/**\n * Snap annotation start/end positions to avoid landing inside HTML tags.\n *\n * If a position falls inside an HTML tag, it is moved:\n * - Start position: snapped to before the tag's `<`\n * - End position: snapped to after the tag's `>`\n *\n * Returns null if the positions can't be safely adjusted (e.g., entirely\n * within a single tag).\n */\nfunction snapOutOfHtmlTags(\n text: string,\n start: number,\n end: number,\n): { start: number; end: number } | null {\n let snappedStart = start\n let snappedEnd = end\n\n const startTag = findContainingTag(text, start)\n if (startTag) {\n snappedStart = startTag.tagStart\n }\n\n const endTag = findContainingTag(text, end)\n if (endTag) {\n snappedEnd = endTag.tagEnd\n }\n\n // Sanity check: start must come before end\n if (snappedStart >= snappedEnd) return null\n\n return { start: snappedStart, end: snappedEnd }\n}\n\n/**\n * Escape HTML entities to prevent XSS injection.\n *\n * Converts special HTML characters to their entity equivalents:\n * - `&` → `&amp;`\n * - `<` → `&lt;`\n * - `>` → `&gt;`\n * - `\"` → `&quot;`\n * - `'` → `&#39;`\n * - `/` → `&#x2F;`\n *\n * @param text - Text to escape\n * @returns Escaped text safe for HTML insertion\n */\nfunction escapeHtmlEntities(text: string): string {\n const map: Record<string, string> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n \"/\": \"&#x2F;\",\n }\n return text.replace(/[&<>\"'/]/g, (char) => map[char])\n}\n"],"mappings":"mEA+CA,SAAgB,EACd,EACA,EACA,EAAgC,EAAE,CAChB,CAClB,GAAM,CACJ,eAAe,GACf,aAAa,GACb,cAAc,GACd,WACA,YACE,EAGE,EAAS,CAAC,GAAG,EAAU,CAAC,MAAM,EAAG,IAAM,CAC3C,IAAM,EAAO,EAAe,EAAE,KAAK,WAAa,EAAE,KAAK,cAEvD,OADa,EAAe,EAAE,KAAK,WAAa,EAAE,KAAK,eACzC,GACd,CAEE,EAAS,EACP,EAAc,IAAI,IAClB,EAAsB,EAAE,CAE9B,IAAK,IAAM,KAAY,EAAQ,CAE7B,IAAI,EACA,EAaJ,GAXI,GAAe,aAAc,GAAY,EAAS,UAEpD,EAAQ,EAAe,EAAS,SAAS,WAAa,EAAS,SAAS,cACxE,EAAM,EAAe,EAAS,SAAS,SAAW,EAAS,SAAS,cAGpE,EAAQ,EAAe,EAAS,KAAK,WAAa,EAAS,KAAK,cAChE,EAAM,EAAe,EAAS,KAAK,SAAW,EAAS,KAAK,aAI1D,CAAC,EAAc,CACjB,IAAM,EAAU,EAAkB,EAAQ,EAAO,EAAI,CACrD,GAAI,IAAY,KAAM,CAEpB,EAAQ,KAAK,EAAS,CACtB,SAEF,EAAQ,EAAQ,MAChB,EAAM,EAAQ,IAGhB,IAAI,EAAS,GAEb,GAAI,EAGF,EAAS,EAAS,EADE,EAAK,UAAU,KAAK,IAAI,EAAG,EAAQ,GAAG,CAAE,KAAK,IAAI,EAAK,OAAQ,EAAM,GAAG,CAAC,CACpD,SAC/B,EAAU,CAEnB,IAAM,EAAe,EAAO,UAAU,EAAO,EAAI,CAC3C,EAAU,EAAa,EAAmB,EAAa,CAAG,EAChE,EAAS,EAAS,OAAS,EAAU,EAAS,WAG9C,SAIF,EAAS,EAAO,MAAM,EAAG,EAAM,CAAG,EAAS,EAAO,MAAM,EAAI,CAG5D,EAAY,IAAI,EAAO,EAAM,CAG/B,MAAO,CAAE,KAAM,EAAQ,cAAa,UAAS,CAO/C,SAAS,EAAkB,EAAc,EAA0D,CAEjG,IAAI,EAAI,EAAM,EACd,KAAO,GAAK,GAAG,CACb,GAAI,EAAK,KAAO,IAAK,OAAO,KAC5B,GAAI,EAAK,KAAO,IAAK,CAEnB,IAAI,EAAI,EACR,KAAO,EAAI,EAAK,QAAQ,CACtB,GAAI,EAAK,KAAO,IAAK,MAAO,CAAE,SAAU,EAAG,OAAQ,EAAI,EAAG,CAC1D,IAGF,MAAO,CAAE,SAAU,EAAG,OAAQ,EAAK,OAAQ,CAE7C,IAEF,OAAO,KAaT,SAAS,EACP,EACA,EACA,EACuC,CACvC,IAAI,EAAe,EACf,EAAa,EAEX,EAAW,EAAkB,EAAM,EAAM,CAC3C,IACF,EAAe,EAAS,UAG1B,IAAM,EAAS,EAAkB,EAAM,EAAI,CAQ3C,OAPI,IACF,EAAa,EAAO,QAIlB,GAAgB,EAAmB,KAEhC,CAAE,MAAO,EAAc,IAAK,EAAY,CAiBjD,SAAS,EAAmB,EAAsB,CAChD,IAAM,EAA8B,CAClC,IAAK,QACL,IAAK,OACL,IAAK,OACL,IAAK,SACL,IAAK,QACL,IAAK,SACN,CACD,OAAO,EAAK,QAAQ,YAAc,GAAS,EAAI,GAAM"}
@@ -1,4 +1,4 @@
1
- import { t as Citation } from "../citation-Cymq3pJ-.cjs";
1
+ import { t as Citation } from "../citation-BB_vC_7x.cjs";
2
2
 
3
3
  //#region src/annotate/types.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { t as Citation } from "../citation-25ZydLsu.mjs";
1
+ import { t as Citation } from "../citation-Bg1QDUYb.mjs";
2
2
 
3
3
  //#region src/annotate/types.d.ts
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/annotate/annotate.ts"],"sourcesContent":["import type { Citation } from '../types/citation'\nimport type { AnnotationOptions, AnnotationResult } from './types'\n\n/**\n * Annotate citations in text with custom markup.\n *\n * Supports two modes:\n * - **Template mode**: Simple before/after wrapping (set `options.template`)\n * - **Callback mode**: Custom logic with full citation context (set `options.callback`)\n *\n * Citations are processed in reverse order to avoid position shifts invalidating\n * subsequent annotations. Position tracking maps original positions to new positions\n * after markup insertion.\n *\n * @param text - Original or cleaned text to annotate\n * @param citations - Citations to mark up (from extraction pipeline)\n * @param options - Annotation configuration\n * @returns Annotated text with position mapping\n *\n * @example Template mode\n * ```typescript\n * const result = annotate(text, citations, {\n * template: { before: '<cite>', after: '</cite>' }\n * })\n * // Result: \"See <cite>500 F.2d 123</cite>\"\n * ```\n *\n * @example Callback mode\n * ```typescript\n * const result = annotate(text, citations, {\n * callback: (citation) => {\n * if (citation.type === 'case') {\n * return `<a href=\"/cases/${citation.volume}\">${citation.matchedText}</a>`\n * }\n * return citation.matchedText\n * }\n * })\n * ```\n *\n * @example Position tracking\n * ```typescript\n * const result = annotate(text, citations, { template: { before: '<mark>', after: '</mark>' } })\n * // result.positionMap tracks how positions shifted\n * const originalPos = 10\n * const newPos = result.positionMap.get(originalPos)\n * ```\n */\nexport function annotate<C extends Citation = Citation>(\n text: string,\n citations: C[],\n options: AnnotationOptions<C> = {}\n): AnnotationResult {\n const {\n useCleanText = false,\n autoEscape = true, // Secure by default\n useFullSpan = false, // Backward compatible default\n template,\n callback,\n } = options\n\n // Sort reverse to avoid position shifts invalidating subsequent annotations\n const sorted = [...citations].sort((a, b) => {\n const aPos = useCleanText ? a.span.cleanStart : a.span.originalStart\n const bPos = useCleanText ? b.span.cleanStart : b.span.originalStart\n return bPos - aPos // Reverse for backward iteration\n })\n\n let result = text\n const positionMap = new Map<number, number>()\n const skipped: Citation[] = []\n\n for (const citation of sorted) {\n // Determine which span to use\n let start: number\n let end: number\n\n if (useFullSpan && 'fullSpan' in citation && citation.fullSpan) {\n // Full span mode: case name through parenthetical\n start = useCleanText ? citation.fullSpan.cleanStart : citation.fullSpan.originalStart\n end = useCleanText ? citation.fullSpan.cleanEnd : citation.fullSpan.originalEnd\n } else {\n // Default mode: core citation only\n start = useCleanText ? citation.span.cleanStart : citation.span.originalStart\n end = useCleanText ? citation.span.cleanEnd : citation.span.originalEnd\n }\n\n // Snap positions out of HTML tags when annotating original text\n if (!useCleanText) {\n const snapped = snapOutOfHtmlTags(result, start, end)\n if (snapped === null) {\n // Could not safely snap — skip this citation\n skipped.push(citation)\n continue\n }\n start = snapped.start\n end = snapped.end\n }\n\n let markup = ''\n\n if (callback) {\n // Callback mode: developer provides full logic\n const surrounding = text.substring(\n Math.max(0, start - 30),\n Math.min(text.length, end + 30)\n )\n markup = callback(citation, surrounding)\n } else if (template) {\n // Template mode: simple before/after wrapping\n const citationText = result.substring(start, end)\n const escaped = autoEscape ? escapeHtmlEntities(citationText) : citationText\n markup = template.before + escaped + template.after\n } else {\n // No annotation specified\n continue\n }\n\n // Insert annotation (working backwards preserves positions for later citations)\n result = result.slice(0, start) + markup + result.slice(end)\n\n // Track original position to new position (before this annotation was added)\n positionMap.set(start, start)\n }\n\n return { text: result, positionMap, skipped }\n}\n\n/**\n * Check if a position falls inside an HTML tag (between `<` and `>`).\n * Returns the index of the opening `<` if inside a tag, otherwise -1.\n */\nfunction findContainingTag(text: string, pos: number): { tagStart: number; tagEnd: number } | null {\n // Search backwards from pos for '<' without encountering '>' first\n let i = pos - 1\n while (i >= 0) {\n if (text[i] === '>') return null // Hit a tag close — we're outside\n if (text[i] === '<') {\n // Found opening '<' — now find the closing '>'\n let j = pos\n while (j < text.length) {\n if (text[j] === '>') return { tagStart: i, tagEnd: j + 1 }\n j++\n }\n // Unclosed tag — treat as inside\n return { tagStart: i, tagEnd: text.length }\n }\n i--\n }\n return null\n}\n\n/**\n * Snap annotation start/end positions to avoid landing inside HTML tags.\n *\n * If a position falls inside an HTML tag, it is moved:\n * - Start position: snapped to before the tag's `<`\n * - End position: snapped to after the tag's `>`\n *\n * Returns null if the positions can't be safely adjusted (e.g., entirely\n * within a single tag).\n */\nfunction snapOutOfHtmlTags(\n text: string,\n start: number,\n end: number,\n): { start: number; end: number } | null {\n let snappedStart = start\n let snappedEnd = end\n\n const startTag = findContainingTag(text, start)\n if (startTag) {\n snappedStart = startTag.tagStart\n }\n\n const endTag = findContainingTag(text, end)\n if (endTag) {\n snappedEnd = endTag.tagEnd\n }\n\n // Sanity check: start must come before end\n if (snappedStart >= snappedEnd) return null\n\n return { start: snappedStart, end: snappedEnd }\n}\n\n/**\n * Escape HTML entities to prevent XSS injection.\n *\n * Converts special HTML characters to their entity equivalents:\n * - `&` → `&amp;`\n * - `<` → `&lt;`\n * - `>` → `&gt;`\n * - `\"` → `&quot;`\n * - `'` → `&#39;`\n * - `/` → `&#x2F;`\n *\n * @param text - Text to escape\n * @returns Escaped text safe for HTML insertion\n */\nfunction escapeHtmlEntities(text: string): string {\n const map: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n }\n return text.replace(/[&<>\"'/]/g, (char) => map[char])\n}\n"],"mappings":"AA+CA,SAAgB,EACd,EACA,EACA,EAAgC,EAAE,CAChB,CAClB,GAAM,CACJ,eAAe,GACf,aAAa,GACb,cAAc,GACd,WACA,YACE,EAGE,EAAS,CAAC,GAAG,EAAU,CAAC,MAAM,EAAG,IAAM,CAC3C,IAAM,EAAO,EAAe,EAAE,KAAK,WAAa,EAAE,KAAK,cAEvD,OADa,EAAe,EAAE,KAAK,WAAa,EAAE,KAAK,eACzC,GACd,CAEE,EAAS,EACP,EAAc,IAAI,IAClB,EAAsB,EAAE,CAE9B,IAAK,IAAM,KAAY,EAAQ,CAE7B,IAAI,EACA,EAaJ,GAXI,GAAe,aAAc,GAAY,EAAS,UAEpD,EAAQ,EAAe,EAAS,SAAS,WAAa,EAAS,SAAS,cACxE,EAAM,EAAe,EAAS,SAAS,SAAW,EAAS,SAAS,cAGpE,EAAQ,EAAe,EAAS,KAAK,WAAa,EAAS,KAAK,cAChE,EAAM,EAAe,EAAS,KAAK,SAAW,EAAS,KAAK,aAI1D,CAAC,EAAc,CACjB,IAAM,EAAU,EAAkB,EAAQ,EAAO,EAAI,CACrD,GAAI,IAAY,KAAM,CAEpB,EAAQ,KAAK,EAAS,CACtB,SAEF,EAAQ,EAAQ,MAChB,EAAM,EAAQ,IAGhB,IAAI,EAAS,GAEb,GAAI,EAMF,EAAS,EAAS,EAJE,EAAK,UACvB,KAAK,IAAI,EAAG,EAAQ,GAAG,CACvB,KAAK,IAAI,EAAK,OAAQ,EAAM,GAAG,CAChC,CACuC,SAC/B,EAAU,CAEnB,IAAM,EAAe,EAAO,UAAU,EAAO,EAAI,CAC3C,EAAU,EAAa,EAAmB,EAAa,CAAG,EAChE,EAAS,EAAS,OAAS,EAAU,EAAS,WAG9C,SAIF,EAAS,EAAO,MAAM,EAAG,EAAM,CAAG,EAAS,EAAO,MAAM,EAAI,CAG5D,EAAY,IAAI,EAAO,EAAM,CAG/B,MAAO,CAAE,KAAM,EAAQ,cAAa,UAAS,CAO/C,SAAS,EAAkB,EAAc,EAA0D,CAEjG,IAAI,EAAI,EAAM,EACd,KAAO,GAAK,GAAG,CACb,GAAI,EAAK,KAAO,IAAK,OAAO,KAC5B,GAAI,EAAK,KAAO,IAAK,CAEnB,IAAI,EAAI,EACR,KAAO,EAAI,EAAK,QAAQ,CACtB,GAAI,EAAK,KAAO,IAAK,MAAO,CAAE,SAAU,EAAG,OAAQ,EAAI,EAAG,CAC1D,IAGF,MAAO,CAAE,SAAU,EAAG,OAAQ,EAAK,OAAQ,CAE7C,IAEF,OAAO,KAaT,SAAS,EACP,EACA,EACA,EACuC,CACvC,IAAI,EAAe,EACf,EAAa,EAEX,EAAW,EAAkB,EAAM,EAAM,CAC3C,IACF,EAAe,EAAS,UAG1B,IAAM,EAAS,EAAkB,EAAM,EAAI,CAQ3C,OAPI,IACF,EAAa,EAAO,QAIlB,GAAgB,EAAmB,KAEhC,CAAE,MAAO,EAAc,IAAK,EAAY,CAiBjD,SAAS,EAAmB,EAAsB,CAChD,IAAM,EAA8B,CAClC,IAAK,QACL,IAAK,OACL,IAAK,OACL,IAAK,SACL,IAAK,QACL,IAAK,SACN,CACD,OAAO,EAAK,QAAQ,YAAc,GAAS,EAAI,GAAM"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/annotate/annotate.ts"],"sourcesContent":["import type { Citation } from \"../types/citation\"\nimport type { AnnotationOptions, AnnotationResult } from \"./types\"\n\n/**\n * Annotate citations in text with custom markup.\n *\n * Supports two modes:\n * - **Template mode**: Simple before/after wrapping (set `options.template`)\n * - **Callback mode**: Custom logic with full citation context (set `options.callback`)\n *\n * Citations are processed in reverse order to avoid position shifts invalidating\n * subsequent annotations. Position tracking maps original positions to new positions\n * after markup insertion.\n *\n * @param text - Original or cleaned text to annotate\n * @param citations - Citations to mark up (from extraction pipeline)\n * @param options - Annotation configuration\n * @returns Annotated text with position mapping\n *\n * @example Template mode\n * ```typescript\n * const result = annotate(text, citations, {\n * template: { before: '<cite>', after: '</cite>' }\n * })\n * // Result: \"See <cite>500 F.2d 123</cite>\"\n * ```\n *\n * @example Callback mode\n * ```typescript\n * const result = annotate(text, citations, {\n * callback: (citation) => {\n * if (citation.type === 'case') {\n * return `<a href=\"/cases/${citation.volume}\">${citation.matchedText}</a>`\n * }\n * return citation.matchedText\n * }\n * })\n * ```\n *\n * @example Position tracking\n * ```typescript\n * const result = annotate(text, citations, { template: { before: '<mark>', after: '</mark>' } })\n * // result.positionMap tracks how positions shifted\n * const originalPos = 10\n * const newPos = result.positionMap.get(originalPos)\n * ```\n */\nexport function annotate<C extends Citation = Citation>(\n text: string,\n citations: C[],\n options: AnnotationOptions<C> = {},\n): AnnotationResult {\n const {\n useCleanText = false,\n autoEscape = true, // Secure by default\n useFullSpan = false, // Backward compatible default\n template,\n callback,\n } = options\n\n // Sort reverse to avoid position shifts invalidating subsequent annotations\n const sorted = [...citations].sort((a, b) => {\n const aPos = useCleanText ? a.span.cleanStart : a.span.originalStart\n const bPos = useCleanText ? b.span.cleanStart : b.span.originalStart\n return bPos - aPos // Reverse for backward iteration\n })\n\n let result = text\n const positionMap = new Map<number, number>()\n const skipped: Citation[] = []\n\n for (const citation of sorted) {\n // Determine which span to use\n let start: number\n let end: number\n\n if (useFullSpan && \"fullSpan\" in citation && citation.fullSpan) {\n // Full span mode: case name through parenthetical\n start = useCleanText ? citation.fullSpan.cleanStart : citation.fullSpan.originalStart\n end = useCleanText ? citation.fullSpan.cleanEnd : citation.fullSpan.originalEnd\n } else {\n // Default mode: core citation only\n start = useCleanText ? citation.span.cleanStart : citation.span.originalStart\n end = useCleanText ? citation.span.cleanEnd : citation.span.originalEnd\n }\n\n // Snap positions out of HTML tags when annotating original text\n if (!useCleanText) {\n const snapped = snapOutOfHtmlTags(result, start, end)\n if (snapped === null) {\n // Could not safely snap — skip this citation\n skipped.push(citation)\n continue\n }\n start = snapped.start\n end = snapped.end\n }\n\n let markup = \"\"\n\n if (callback) {\n // Callback mode: developer provides full logic\n const surrounding = text.substring(Math.max(0, start - 30), Math.min(text.length, end + 30))\n markup = callback(citation, surrounding)\n } else if (template) {\n // Template mode: simple before/after wrapping\n const citationText = result.substring(start, end)\n const escaped = autoEscape ? escapeHtmlEntities(citationText) : citationText\n markup = template.before + escaped + template.after\n } else {\n // No annotation specified\n continue\n }\n\n // Insert annotation (working backwards preserves positions for later citations)\n result = result.slice(0, start) + markup + result.slice(end)\n\n // Track original position to new position (before this annotation was added)\n positionMap.set(start, start)\n }\n\n return { text: result, positionMap, skipped }\n}\n\n/**\n * Check if a position falls inside an HTML tag (between `<` and `>`).\n * Returns the index of the opening `<` if inside a tag, otherwise -1.\n */\nfunction findContainingTag(text: string, pos: number): { tagStart: number; tagEnd: number } | null {\n // Search backwards from pos for '<' without encountering '>' first\n let i = pos - 1\n while (i >= 0) {\n if (text[i] === \">\") return null // Hit a tag close — we're outside\n if (text[i] === \"<\") {\n // Found opening '<' — now find the closing '>'\n let j = pos\n while (j < text.length) {\n if (text[j] === \">\") return { tagStart: i, tagEnd: j + 1 }\n j++\n }\n // Unclosed tag — treat as inside\n return { tagStart: i, tagEnd: text.length }\n }\n i--\n }\n return null\n}\n\n/**\n * Snap annotation start/end positions to avoid landing inside HTML tags.\n *\n * If a position falls inside an HTML tag, it is moved:\n * - Start position: snapped to before the tag's `<`\n * - End position: snapped to after the tag's `>`\n *\n * Returns null if the positions can't be safely adjusted (e.g., entirely\n * within a single tag).\n */\nfunction snapOutOfHtmlTags(\n text: string,\n start: number,\n end: number,\n): { start: number; end: number } | null {\n let snappedStart = start\n let snappedEnd = end\n\n const startTag = findContainingTag(text, start)\n if (startTag) {\n snappedStart = startTag.tagStart\n }\n\n const endTag = findContainingTag(text, end)\n if (endTag) {\n snappedEnd = endTag.tagEnd\n }\n\n // Sanity check: start must come before end\n if (snappedStart >= snappedEnd) return null\n\n return { start: snappedStart, end: snappedEnd }\n}\n\n/**\n * Escape HTML entities to prevent XSS injection.\n *\n * Converts special HTML characters to their entity equivalents:\n * - `&` → `&amp;`\n * - `<` → `&lt;`\n * - `>` → `&gt;`\n * - `\"` → `&quot;`\n * - `'` → `&#39;`\n * - `/` → `&#x2F;`\n *\n * @param text - Text to escape\n * @returns Escaped text safe for HTML insertion\n */\nfunction escapeHtmlEntities(text: string): string {\n const map: Record<string, string> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n \"/\": \"&#x2F;\",\n }\n return text.replace(/[&<>\"'/]/g, (char) => map[char])\n}\n"],"mappings":"AA+CA,SAAgB,EACd,EACA,EACA,EAAgC,EAAE,CAChB,CAClB,GAAM,CACJ,eAAe,GACf,aAAa,GACb,cAAc,GACd,WACA,YACE,EAGE,EAAS,CAAC,GAAG,EAAU,CAAC,MAAM,EAAG,IAAM,CAC3C,IAAM,EAAO,EAAe,EAAE,KAAK,WAAa,EAAE,KAAK,cAEvD,OADa,EAAe,EAAE,KAAK,WAAa,EAAE,KAAK,eACzC,GACd,CAEE,EAAS,EACP,EAAc,IAAI,IAClB,EAAsB,EAAE,CAE9B,IAAK,IAAM,KAAY,EAAQ,CAE7B,IAAI,EACA,EAaJ,GAXI,GAAe,aAAc,GAAY,EAAS,UAEpD,EAAQ,EAAe,EAAS,SAAS,WAAa,EAAS,SAAS,cACxE,EAAM,EAAe,EAAS,SAAS,SAAW,EAAS,SAAS,cAGpE,EAAQ,EAAe,EAAS,KAAK,WAAa,EAAS,KAAK,cAChE,EAAM,EAAe,EAAS,KAAK,SAAW,EAAS,KAAK,aAI1D,CAAC,EAAc,CACjB,IAAM,EAAU,EAAkB,EAAQ,EAAO,EAAI,CACrD,GAAI,IAAY,KAAM,CAEpB,EAAQ,KAAK,EAAS,CACtB,SAEF,EAAQ,EAAQ,MAChB,EAAM,EAAQ,IAGhB,IAAI,EAAS,GAEb,GAAI,EAGF,EAAS,EAAS,EADE,EAAK,UAAU,KAAK,IAAI,EAAG,EAAQ,GAAG,CAAE,KAAK,IAAI,EAAK,OAAQ,EAAM,GAAG,CAAC,CACpD,SAC/B,EAAU,CAEnB,IAAM,EAAe,EAAO,UAAU,EAAO,EAAI,CAC3C,EAAU,EAAa,EAAmB,EAAa,CAAG,EAChE,EAAS,EAAS,OAAS,EAAU,EAAS,WAG9C,SAIF,EAAS,EAAO,MAAM,EAAG,EAAM,CAAG,EAAS,EAAO,MAAM,EAAI,CAG5D,EAAY,IAAI,EAAO,EAAM,CAG/B,MAAO,CAAE,KAAM,EAAQ,cAAa,UAAS,CAO/C,SAAS,EAAkB,EAAc,EAA0D,CAEjG,IAAI,EAAI,EAAM,EACd,KAAO,GAAK,GAAG,CACb,GAAI,EAAK,KAAO,IAAK,OAAO,KAC5B,GAAI,EAAK,KAAO,IAAK,CAEnB,IAAI,EAAI,EACR,KAAO,EAAI,EAAK,QAAQ,CACtB,GAAI,EAAK,KAAO,IAAK,MAAO,CAAE,SAAU,EAAG,OAAQ,EAAI,EAAG,CAC1D,IAGF,MAAO,CAAE,SAAU,EAAG,OAAQ,EAAK,OAAQ,CAE7C,IAEF,OAAO,KAaT,SAAS,EACP,EACA,EACA,EACuC,CACvC,IAAI,EAAe,EACf,EAAa,EAEX,EAAW,EAAkB,EAAM,EAAM,CAC3C,IACF,EAAe,EAAS,UAG1B,IAAM,EAAS,EAAkB,EAAM,EAAI,CAQ3C,OAPI,IACF,EAAa,EAAO,QAIlB,GAAgB,EAAmB,KAEhC,CAAE,MAAO,EAAc,IAAK,EAAY,CAiBjD,SAAS,EAAmB,EAAsB,CAChD,IAAM,EAA8B,CAClC,IAAK,QACL,IAAK,OACL,IAAK,OACL,IAAK,SACL,IAAK,QACL,IAAK,SACN,CACD,OAAO,EAAK,QAAQ,YAAc,GAAS,EAAI,GAAM"}
@@ -1,3 +1,70 @@
1
+ //#region src/extract/pincite.d.ts
2
+ /**
3
+ * Structured pincite information parsed from citation text.
4
+ */
5
+ interface PinciteInfo {
6
+ /** Primary page number */
7
+ page: number;
8
+ /** End page for ranges: "570-75" → 575 */
9
+ endPage?: number;
10
+ /** Footnote number: "570 n.3" → 3 */
11
+ footnote?: number;
12
+ /** True if this is a page range */
13
+ isRange: boolean;
14
+ /** Original text before parsing */
15
+ raw: string;
16
+ }
17
+ /**
18
+ * Parse a pincite string into structured components.
19
+ *
20
+ * Handles simple pages, ranges (with abbreviated end pages),
21
+ * footnote references, and "at" prefixes.
22
+ *
23
+ * @example
24
+ * parsePincite("570") // { page: 570, isRange: false, raw: "570" }
25
+ * parsePincite("570-75") // { page: 570, endPage: 575, isRange: true, raw: "570-75" }
26
+ * parsePincite("570 n.3") // { page: 570, footnote: 3, isRange: false, raw: "570 n.3" }
27
+ *
28
+ * @returns Parsed pincite info, or null if unparseable
29
+ */
30
+ declare function parsePincite(raw: string): PinciteInfo | null;
31
+ //#endregion
32
+ //#region src/clean/segmentMap.d.ts
33
+ /**
34
+ * Segment-based position mapping.
35
+ *
36
+ * Compresses a per-character position map into contiguous segments where the
37
+ * offset between clean and original coordinates is constant. Lookups use
38
+ * binary search (O(log k) where k = number of segments, typically 50-200).
39
+ */
40
+ interface Segment {
41
+ /** Start position in clean text */
42
+ cleanPos: number;
43
+ /** Corresponding start position in original text */
44
+ origPos: number;
45
+ /** Number of positions covered by this segment */
46
+ len: number;
47
+ }
48
+ declare class SegmentMap {
49
+ readonly segments: readonly Segment[];
50
+ constructor(segments: Segment[]);
51
+ /**
52
+ * Create an identity map (clean position === original position).
53
+ */
54
+ static identity(length: number): SegmentMap;
55
+ /**
56
+ * Compress a per-position Map into a SegmentMap.
57
+ * Adjacent entries with the same offset (origPos - cleanPos) are merged
58
+ * into a single segment.
59
+ */
60
+ static fromMap(map: Map<number, number>): SegmentMap;
61
+ /**
62
+ * Look up the original position for a clean-text position.
63
+ * Uses binary search on sorted segments.
64
+ */
65
+ lookup(cleanPos: number): number;
66
+ }
67
+ //#endregion
1
68
  //#region src/types/span.d.ts
2
69
  /**
3
70
  * Represents a text span with positions tracked through transformations.
@@ -37,13 +104,15 @@ interface TransformationMap {
37
104
  cleanToOriginal: Map<number, number>;
38
105
  /** Maps original text position to cleaned text position */
39
106
  originalToClean: Map<number, number>;
107
+ /** Compressed segment-based clean→original mapping for O(log k) lookup */
108
+ cleanToOriginalSegments?: SegmentMap;
40
109
  }
41
110
  //#endregion
42
111
  //#region src/types/citation.d.ts
43
112
  /**
44
113
  * Citation type discriminator for type-safe pattern matching.
45
114
  */
46
- type CitationType = "case" | "statute" | "journal" | "neutral" | "publicLaw" | "federalRegister" | "statutesAtLarge" | "id" | "supra" | "shortFormCase";
115
+ type CitationType = "case" | "statute" | "journal" | "neutral" | "publicLaw" | "federalRegister" | "statutesAtLarge" | "constitutional" | "id" | "supra" | "shortFormCase";
47
116
  /**
48
117
  * Warning generated during citation parsing.
49
118
  */
@@ -61,6 +130,11 @@ interface Warning {
61
130
  context?: string;
62
131
  }
63
132
  /**
133
+ * Introductory signal word classification for citation support level.
134
+ * Based on Bluebook signal categories (Rule 1.2).
135
+ */
136
+ type CitationSignal = "see" | "see also" | "see generally" | "cf" | "but see" | "but cf" | "compare" | "accord" | "contra";
137
+ /**
64
138
  * Base fields shared by all citation types.
65
139
  */
66
140
  interface CitationBase {
@@ -84,6 +158,75 @@ interface CitationBase {
84
158
  patternsChecked: number;
85
159
  /** Warnings for malformed or ambiguous regions */
86
160
  warnings?: Warning[];
161
+ /** Introductory signal word (e.g., "see", "see also", "but see") */
162
+ signal?: CitationSignal;
163
+ /** Group ID for string citations sharing the same proposition */
164
+ stringCitationGroupId?: string;
165
+ /** Position within the string citation group (0-indexed) */
166
+ stringCitationIndex?: number;
167
+ /** Total number of citations in this string citation group */
168
+ stringCitationGroupSize?: number;
169
+ /** Whether this citation appears in a footnote (only populated when detectFootnotes enabled) */
170
+ inFootnote?: boolean;
171
+ /** Footnote number, if applicable (only populated when detectFootnotes enabled) */
172
+ footnoteNumber?: number;
173
+ }
174
+ /**
175
+ * Court level and jurisdiction inferred from reporter series.
176
+ * Populated independently of the parenthetical-extracted `court` field.
177
+ */
178
+ interface CourtInference {
179
+ /** Court level classification */
180
+ level: "supreme" | "appellate" | "trial" | "unknown";
181
+ /** Jurisdiction classification */
182
+ jurisdiction: "federal" | "state" | "unknown";
183
+ /** 2-letter state code, only for state-specific reporters */
184
+ state?: string;
185
+ /** Confidence score 0.0-1.0 (1.0 for unambiguous, 0.7 for regional multi-state) */
186
+ confidence: number;
187
+ }
188
+ /**
189
+ * Signal-word classification for explanatory parentheticals.
190
+ * Based on the leading gerund/verb form in the parenthetical text.
191
+ */
192
+ type ParentheticalType = "holding" | "finding" | "stating" | "noting" | "explaining" | "quoting" | "citing" | "discussing" | "describing" | "recognizing" | "applying" | "rejecting" | "adopting" | "requiring" | "other";
193
+ /**
194
+ * An extracted explanatory parenthetical from a case citation.
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * { text: "holding that X requires Y", type: "holding" }
199
+ * { text: "citing Doe v. City for the same proposition", type: "citing" }
200
+ * ```
201
+ */
202
+ interface Parenthetical {
203
+ /** Full text content between the parentheses (excluding parens themselves) */
204
+ text: string;
205
+ /** Signal-word classification based on leading gerund */
206
+ type: ParentheticalType;
207
+ }
208
+ /**
209
+ * Normalized subsequent history signal classification.
210
+ * Maps variant spellings (aff'd, affirmed) to canonical forms.
211
+ */
212
+ type HistorySignal = "affirmed" | "reversed" | "cert_denied" | "cert_granted" | "overruled" | "vacated" | "remanded" | "modified" | "abrogated" | "superseded" | "disapproved" | "questioned" | "distinguished" | "withdrawn" | "reinstated";
213
+ /**
214
+ * A single subsequent history entry from a case citation.
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * { signal: "affirmed", rawSignal: "aff'd", signalSpan: { ... }, order: 0 }
219
+ * ```
220
+ */
221
+ interface SubsequentHistoryEntry {
222
+ /** Normalized signal classification */
223
+ signal: HistorySignal;
224
+ /** Raw signal text as it appeared in the document */
225
+ rawSignal: string;
226
+ /** Position of the signal text in the document */
227
+ signalSpan: Span;
228
+ /** Order in the history chain (0-based) */
229
+ order: number;
87
230
  }
88
231
  /**
89
232
  * Full case citation (volume-reporter-page format).
@@ -98,7 +241,11 @@ interface FullCaseCitation extends CitationBase {
98
241
  /** Page number — optional for blank page placeholder citations (e.g., "___" or "---") */
99
242
  page?: number;
100
243
  pincite?: number;
244
+ /** Structured pincite information (page, range, footnote) */
245
+ pinciteInfo?: PinciteInfo;
101
246
  court?: string;
247
+ /** Normalized court string: spaces collapsed, trailing period ensured */
248
+ normalizedCourt?: string;
102
249
  year?: number;
103
250
  /** Normalized reporter abbreviation from reporters-db (e.g., "F.2d" vs "F. 2d") */
104
251
  normalizedReporter?: string;
@@ -116,12 +263,29 @@ interface FullCaseCitation extends CitationBase {
116
263
  reporter: string;
117
264
  page: number;
118
265
  }>;
119
- /** Citation signal (introductory phrase) */
120
- signal?: "see" | "see also" | "cf" | "but see" | "compare";
121
- /** Parenthetical explanation following the citation */
122
- parenthetical?: string;
123
- /** Subsequent procedural history (e.g., "aff'd", "rev'd", "cert. denied") */
124
- subsequentHistory?: string;
266
+ /**
267
+ * Explanatory parentheticals following the citation.
268
+ * Only populated when explanatory content is found (not court/year/disposition).
269
+ * @example [{ text: "holding that X requires Y", type: "holding" }]
270
+ */
271
+ parentheticals?: Parenthetical[];
272
+ /**
273
+ * Subsequent history entries for this citation.
274
+ * Each entry describes a procedural event (affirmed, reversed, etc.).
275
+ * Only populated on the parent (original) citation.
276
+ * @example [{ signal: "affirmed", rawSignal: "aff'd", signalSpan: {...}, order: 0 }]
277
+ */
278
+ subsequentHistoryEntries?: SubsequentHistoryEntry[];
279
+ /**
280
+ * Back-pointer indicating this citation is a subsequent history citation.
281
+ * `index` is the parent's position in the results array returned by
282
+ * `extractCitations()` — it becomes invalid if the array is filtered or reordered.
283
+ * @example { index: 0, signal: "affirmed" }
284
+ */
285
+ subsequentHistoryOf?: {
286
+ index: number;
287
+ signal: HistorySignal;
288
+ };
125
289
  /**
126
290
  * Date information in multiple formats.
127
291
  * - iso: ISO 8601 format (YYYY-MM-DD or YYYY-MM-DDTHH:mm:ssZ)
@@ -189,6 +353,18 @@ interface FullCaseCitation extends CitationBase {
189
353
  */
190
354
  proceduralPrefix?: string;
191
355
  /**
356
+ * Nominative (historical) reporter volume for early SCOTUS citations.
357
+ * Present only when citation includes a nominative parenthetical, e.g.,
358
+ * `67 U.S. (2 Black) 635` → nominativeVolume: 2
359
+ */
360
+ nominativeVolume?: number;
361
+ /**
362
+ * Nominative (historical) reporter abbreviation for early SCOTUS citations.
363
+ * Present only when citation includes a nominative parenthetical, e.g.,
364
+ * `67 U.S. (2 Black) 635` → nominativeReporter: "Black"
365
+ */
366
+ nominativeReporter?: string;
367
+ /**
192
368
  * True when page position contains a blank placeholder ("___" or "---").
193
369
  * Populated by Phase 5 (Blank Page support).
194
370
  * When true, page field will be undefined and confidence reduced to 0.8.
@@ -200,6 +376,13 @@ interface FullCaseCitation extends CitationBase {
200
376
  * @example "en banc", "per curiam"
201
377
  */
202
378
  disposition?: string;
379
+ /**
380
+ * Court level/jurisdiction inferred from reporter series.
381
+ * Always populated independently of the parenthetical `court` field.
382
+ * Uses a curated static lookup table — does not depend on the reporter DB
383
+ * to preserve tree-shaking of the `eyecite-ts/data` entry point.
384
+ */
385
+ inferredCourt?: CourtInference;
203
386
  }
204
387
  /**
205
388
  * Statute citation (U.S. Code, state codes, etc.).
@@ -314,6 +497,26 @@ interface StatutesAtLargeCitation extends CitationBase {
314
497
  year?: number;
315
498
  }
316
499
  /**
500
+ * Constitutional citation (U.S. or state constitution).
501
+ *
502
+ * @example "U.S. Const. art. III, § 2"
503
+ * @example "U.S. Const. amend. XIV, § 1"
504
+ * @example "Cal. Const. art. I, § 7"
505
+ */
506
+ interface ConstitutionalCitation extends CitationBase {
507
+ type: "constitutional";
508
+ /** Jurisdiction code: "US", 2-letter state code, or undefined for bare "Const." */
509
+ jurisdiction?: string;
510
+ /** Article number (parsed from Roman numerals) — mutually exclusive with amendment */
511
+ article?: number;
512
+ /** Amendment number (parsed from Roman numerals) — mutually exclusive with article */
513
+ amendment?: number;
514
+ /** Section identifier (string to handle non-numeric like "3-a") */
515
+ section?: string;
516
+ /** Clause number (always numeric) */
517
+ clause?: number;
518
+ }
519
+ /**
317
520
  * Id. citation (refers to immediately preceding citation).
318
521
  *
319
522
  * @example "Id."
@@ -366,16 +569,16 @@ interface ShortFormCaseCitation extends CitationBase {
366
569
  * // ...
367
570
  * }
368
571
  */
369
- type Citation = FullCaseCitation | StatuteCitation | JournalCitation | NeutralCitation | PublicLawCitation | FederalRegisterCitation | StatutesAtLargeCitation | IdCitation | SupraCitation | ShortFormCaseCitation;
572
+ type Citation = FullCaseCitation | StatuteCitation | JournalCitation | NeutralCitation | PublicLawCitation | FederalRegisterCitation | StatutesAtLargeCitation | ConstitutionalCitation | IdCitation | SupraCitation | ShortFormCaseCitation;
370
573
  /**
371
574
  * Citation type discriminators grouped by category.
372
575
  */
373
- type FullCitationType = "case" | "statute" | "journal" | "neutral" | "publicLaw" | "federalRegister" | "statutesAtLarge";
576
+ type FullCitationType = "case" | "statute" | "journal" | "neutral" | "publicLaw" | "federalRegister" | "statutesAtLarge" | "constitutional";
374
577
  type ShortFormCitationType = "id" | "supra" | "shortFormCase";
375
578
  /**
376
579
  * Union of all full citation types (not short-form references).
377
580
  */
378
- type FullCitation = FullCaseCitation | StatuteCitation | JournalCitation | NeutralCitation | PublicLawCitation | FederalRegisterCitation | StatutesAtLargeCitation;
581
+ type FullCitation = FullCaseCitation | StatuteCitation | JournalCitation | NeutralCitation | PublicLawCitation | FederalRegisterCitation | StatutesAtLargeCitation | ConstitutionalCitation;
379
582
  /**
380
583
  * Union of all short-form citation types (Id., supra, short-form case).
381
584
  */
@@ -398,5 +601,5 @@ type CitationOfType<T extends CitationType> = Extract<Citation, {
398
601
  */
399
602
  type ExtractorMap = { [K in FullCitationType]: CitationOfType<K> };
400
603
  //#endregion
401
- export { TransformationMap as S, StatuteCitation as _, ExtractorMap as a, Warning as b, FullCitation as c, JournalCitation as d, NeutralCitation as f, ShortFormCitationType as g, ShortFormCitation as h, CitationType as i, FullCitationType as l, ShortFormCaseCitation as m, CitationBase as n, FederalRegisterCitation as o, PublicLawCitation as p, CitationOfType as r, FullCaseCitation as s, Citation as t, IdCitation as u, StatutesAtLargeCitation as v, Span as x, SupraCitation as y };
402
- //# sourceMappingURL=citation-25ZydLsu.d.mts.map
604
+ export { PinciteInfo as A, StatuteCitation as C, Warning as D, SupraCitation as E, Span as O, ShortFormCitationType as S, SubsequentHistoryEntry as T, Parenthetical as _, CitationType as a, ShortFormCaseCitation as b, ExtractorMap as c, FullCitation as d, FullCitationType as f, NeutralCitation as g, JournalCitation as h, CitationSignal as i, parsePincite as j, TransformationMap as k, FederalRegisterCitation as l, IdCitation as m, CitationBase as n, ConstitutionalCitation as o, HistorySignal as p, CitationOfType as r, CourtInference as s, Citation as t, FullCaseCitation as u, ParentheticalType as v, StatutesAtLargeCitation as w, ShortFormCitation as x, PublicLawCitation as y };
605
+ //# sourceMappingURL=citation-BB_vC_7x.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citation-BB_vC_7x.d.cts","names":[],"sources":["../src/extract/pincite.ts","../src/clean/segmentMap.ts","../src/types/span.ts","../src/types/citation.ts"],"mappings":";;AAGA;;UAAiB,WAAA;EAAA;EAEf,IAAA;;EAEA,OAAA;;EAEA,QAAA;;EAEA,OAAA;EAsBF;EApBE,GAAA;AAAA;;;;;;ACLF;;;;;;;;iBDyBgB,YAAA,CAAa,GAAA,WAAc,WAAA;;;;AA9B3C;;;;;;UCKiB,OAAA;;EAEf,QAAA;;EAEA,OAAA;EDqBF;ECnBE,GAAA;AAAA;AAAA,cAGW,UAAA;EAAA,SACF,QAAA,WAAmB,OAAA;EAE5B,WAAA,CAAY,QAAA,EAAU,OAAA;;;AAZxB;SAmBS,QAAA,CAAS,MAAA,WAAiB,UAAA;;;;;;SAS1B,OAAA,CAAQ,GAAA,EAAK,GAAA,mBAAsB,UAAA;EAtB1C;AAGF;;;EAqDE,MAAA,CAAO,QAAA;AAAA;;;;ADnET;;;;;;;;;;;AA8BA;;;;;UEhBiB,IAAA;;EAEf,UAAA;EDXF;ECcE,QAAA;;EAGA,aAAA;;EAGA,WAAA;AAAA;;;ADXF;;;;UCoBiB,iBAAA;;EAEf,eAAA,EAAiB,GAAA;;EAGjB,eAAA,EAAiB,GAAA;EDNyB;ECS1C,uBAAA,GAHiB,UAAA;AAAA;;;AFvCnB;;;AAAA,KGEY,YAAA;;;;UAgBK,OAAA;;EAEf,KAAA;EHVA;EGYA,OAAA;EHQc;EGNd,QAAA;IAAY,KAAA;IAAe,GAAA;EAAA;;EAE3B,OAAA;AAAA;;;;;KAOU,cAAA;;;;UAcK,YAAA;EFjCJ;EEmCX,IAAA;;EAGA,IAAA,EAAM,IAAA;;;;;;;;EASN,UAAA;;EAGA,WAAA;;EAGA,aAAA;;EAGA,eAAA;;EAGA,QAAA,GAAW,OAAA;;EAGX,MAAA,GAAS,cAAA;;EAGT,qBAAA;EFZO;EEeP,mBAAA;;EAGA,uBAAA;EDvEF;EC0EE,UAAA;;EAGA,cAAA;AAAA;;;;;UAOe,cAAA;EDhEjB;ECkEE,KAAA;;EAEA,YAAA;;EAEA,KAAA;;EAEA,UAAA;AAAA;;;;;KAOU,iBAAA;;;;;;AA/GZ;;;;UAyIiB,aAAA;EAzHjB;EA2HE,IAAA;;EAEA,IAAA,EAAM,iBAAA;AAAA;;;;;KAOI,aAAA;;;AArHZ;;;;;AAcA;UAgIiB,sBAAA;;EAEf,MAAA,EAAQ,aAAA;;EAER,SAAA;;EAEA,UAAA,EAAY,IAAA;;EAEZ,KAAA;AAAA;;;;;;;UASe,gBAAA,SAAyB,YAAA;EACxC,IAAA;EACA,MAAA;EACA,QAAA;;EAEA,IAAA;EACA,OAAA;;EAEA,WAAA,GARe,WAAA;EASf,KAAA;EAvGe;EAyGf,eAAA;EACA,IAAA;;EAGA,kBAAA;;;;;AA9FF;;;EAuGE,OAAA;EAvGU;EA0GV,iBAAA,GAAoB,KAAA;IAClB,MAAA;IACA,QAAA;IACA,IAAA;EAAA;;;;;AAxEJ;EAgFE,cAAA,GAAiB,aAAA;;;;AAvDnB;;;EA+DE,wBAAA,GAA2B,sBAAA;;;;;;;EAQ3B,mBAAA;IAAwB,KAAA;IAAe,MAAA,EAAQ,aAAA;EAAA;;;;;;EAO/C,IAAA;IACE,GAAA;IACA,MAAA;MAAW,IAAA;MAAc,KAAA;MAAgB,GAAA;IAAA;EAAA;;;;;EAO3C,uBAAA,GAA0B,KAAA;IACxB,MAAA;IACA,QAAA;IACA,IAAA;IACA,UAAA;IACA,MAAA;EAAA;;;;;;EAQF,QAAA,GAAW,IAAA;;;;;;EAOX,QAAA;;;;;;EAOA,SAAA;;;;;;EAOA,SAAA;;;;;;EAOA,mBAAA;;;;;;EAOA,mBAAA;;;;;;EAOA,gBAAA;EAoCgB;AASlB;;;;EAtCE,gBAAA;;;;;;EAOA,kBAAA;;;;;AA0DF;EAnDE,YAAA;;;;;;EAOA,WAAA;;;;;;;EAQA,aAAA,GAAgB,cAAA;AAAA;AAgElB;;;;;;AAAA,UAvDiB,eAAA,SAAwB,YAAA;EACvC,IAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EAqEF;EAnEE,UAAA;;EAEA,YAAA;;;;;;EAMA,OAAA;EAkEA;EAhEA,QAAA;AAAA;;;;;;;;;UAWe,eAAA,SAAwB,YAAA;EACvC,IAAA;EA0Ee;EAxEf,MAAA;EAwE+C;EAtE/C,KAAA;;EAEA,MAAA;;EAEA,OAAA;;EAEA,YAAA;EAiFF;EA/EE,IAAA;;EAEA,OAAA;;EAEA,IAAA;AAAA;;;;;;;AA+FF;;UApFiB,eAAA,SAAwB,YAAA;EACvC,IAAA;;EAEA,IAAA;;EAEA,KAAA;EAiFA;EA/EA,cAAA;AAAA;;;;;;;;;UAWe,iBAAA,SAA0B,YAAA;EACzC,IAAA;;EAEA,QAAA;;EAEA,SAAA;;EAEA,KAAA;AAAA;;;;AA6GF;;;;;UAlGiB,uBAAA,SAAgC,YAAA;EAC/C,IAAA;;EAEA,MAAA;;EAEA,IAAA;;EAEA,IAAA;AAAA;;UAIe,uBAAA,SAAgC,YAAA;EAC/C,IAAA;;EAEA,MAAA;;EAEA,IAAA;;EAEA,IAAA;AAAA;;;;;;AAgGF;;UAtFiB,sBAAA,SAA+B,YAAA;EAC9C,IAAA;EAqFU;EAnFV,YAAA;EA4FU;EA1FV,OAAA;EA0FU;EAxFV,SAAA;EA6FF;EA3FE,OAAA;;EAEA,MAAA;AAAA;;;;;;;UASe,UAAA,SAAmB,YAAA;EAClC,IAAA;EACA,OAAA;AAAA;;;;;;;UASe,aAAA,SAAsB,YAAA;EACrC,IAAA;EAiFF;EA/EE,SAAA;;EAEA,OAAA;AAAA;;;;;;;UASe,qBAAA,SAA8B,YAAA;EAC7C,IAAA;EACA,MAAA;EACA,QAAA;EACA,IAAA;EACA,OAAA;AAAA;;;;;;;;;;;;;AAgFF;;;;;KA5DY,QAAA,GACR,gBAAA,GACA,eAAA,GACA,eAAA,GACA,eAAA,GACA,iBAAA,GACA,uBAAA,GACA,uBAAA,GACA,sBAAA,GACA,UAAA,GACA,aAAA,GACA,qBAAA;;;;KAKQ,gBAAA;AAAA,KASA,qBAAA;;;;KAKA,YAAA,GACR,gBAAA,GACA,eAAA,GACA,eAAA,GACA,eAAA,GACA,iBAAA,GACA,uBAAA,GACA,uBAAA,GACA,sBAAA;;;;KAKQ,iBAAA,GAAoB,UAAA,GAAa,aAAA,GAAgB,qBAAA;;;;;;;;;;KAWjD,cAAA,WAAyB,YAAA,IAAgB,OAAA,CAAQ,QAAA;EAAY,IAAA,EAAM,CAAA;AAAA;;;;;KAMnE,YAAA,WACJ,gBAAA,GAAmB,cAAA,CAAe,CAAA"}