You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
5.0 KiB
173 lines
5.0 KiB
/** |
|
* Bookstr parsing utilities |
|
* Ported from wikistr/src/lib/books.ts for use in jumble |
|
*/ |
|
|
|
export interface BookReference { |
|
book: string |
|
chapter?: number |
|
verse?: string // Can be "1", "1-3", "1,3,5", etc. |
|
version?: string |
|
} |
|
|
|
/** |
|
* Normalize string according to NIP-54 rules |
|
*/ |
|
function normalizeNip54(text: string): string { |
|
return text |
|
.replace(/['"]/g, '') // Remove quotes |
|
.replace(/[^a-zA-Z0-9]/g, (char) => { |
|
if (/[a-zA-Z]/.test(char)) { |
|
return char.toLowerCase() |
|
} |
|
if (/[0-9]/.test(char)) { |
|
return char |
|
} |
|
return '-' |
|
}) |
|
.toLowerCase() |
|
.replace(/-+/g, '-') // Collapse multiple hyphens |
|
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens |
|
} |
|
|
|
/** |
|
* Parse book wikilink notation according to NKBIP-08 |
|
* Format: "[[book::collection | title chapter:section | version]]" |
|
*/ |
|
export function parseBookWikilink(wikilink: string): { references: BookReference[], versions?: string[], bookType?: string } | null { |
|
// Remove the [[ and ]] brackets |
|
const content = wikilink.replace(/^\[\[|\]\]$/g, '') |
|
|
|
// Must start with book:: |
|
if (!content.startsWith('book::')) { |
|
return null |
|
} |
|
|
|
// Format: book::collection | title chapter:section | version |
|
const bookContent = content.substring(6).trim() // Remove "book::" |
|
|
|
// Split by pipes to parse structure |
|
const pipeParts = bookContent.split(/\s+\|\s+/) |
|
|
|
let collection: string | undefined |
|
let titlePart = '' |
|
let versionPart = '' |
|
|
|
if (pipeParts.length === 1) { |
|
// No pipes: just title (e.g., "book::genesis") |
|
titlePart = pipeParts[0] |
|
} else if (pipeParts.length === 2) { |
|
// One pipe: could be "collection | title" or "title chapter | version" |
|
const first = pipeParts[0].trim() |
|
const second = pipeParts[1].trim() |
|
|
|
// Check if first part has chapter/section (indicates it's title chapter | version) |
|
const hasChapterSection = first.match(/:\d+/) || first.match(/\s+\d+(\s|$)/) |
|
|
|
if (hasChapterSection) { |
|
// Format: "title chapter | version" |
|
titlePart = first |
|
versionPart = second |
|
} else { |
|
// Format: "collection | title" |
|
collection = normalizeNip54(first) |
|
titlePart = second |
|
} |
|
} else { |
|
// Multiple pipes: "collection | title chapter | version" |
|
collection = normalizeNip54(pipeParts[0].trim()) |
|
titlePart = pipeParts.slice(1, -1).join(' | ') |
|
versionPart = pipeParts[pipeParts.length - 1].trim() |
|
} |
|
|
|
// Parse versions first (needed for references) |
|
const versions = versionPart ? versionPart.split(/\s+/).map(v => normalizeNip54(v).toUpperCase()).filter(v => v) : undefined |
|
|
|
// Parse multiple references from titlePart (e.g., "romans 1:16-17, psalms 23:1") |
|
// Split by comma to handle multiple book references |
|
const referenceStrings = titlePart.split(',').map(s => s.trim()).filter(s => s) |
|
const references: BookReference[] = [] |
|
|
|
for (const refString of referenceStrings) { |
|
// Parse each reference: "book chapter:verse" or "book chapter" or "book" |
|
const chapterSectionMatch = refString.match(/^(.+?)\s+(\d+|[a-zA-Z0-9_-]+)(?::(.+))?$/) |
|
|
|
let title = '' |
|
let chapter: number | undefined |
|
let verse: string | undefined |
|
|
|
if (chapterSectionMatch) { |
|
title = normalizeNip54(chapterSectionMatch[1].trim()) |
|
const chapterStr = chapterSectionMatch[2] |
|
chapter = /^\d+$/.test(chapterStr) ? parseInt(chapterStr, 10) : undefined |
|
if (chapterSectionMatch[3]) { |
|
verse = chapterSectionMatch[3].trim() |
|
} |
|
} else { |
|
title = normalizeNip54(refString) |
|
} |
|
|
|
// Create reference |
|
const reference: BookReference = { |
|
book: title |
|
} |
|
if (chapter !== undefined) { |
|
reference.chapter = chapter |
|
} |
|
if (verse) { |
|
reference.verse = verse |
|
} |
|
if (versions && versions.length > 0) { |
|
reference.version = versions[0] // Use first version for backward compatibility |
|
} |
|
|
|
references.push(reference) |
|
} |
|
|
|
// Use collection as bookType (e.g., "bible", "quran", "torah") |
|
// If no collection, default to "book" |
|
const inferredBookType = collection || 'book' |
|
|
|
return { references, versions, bookType: inferredBookType } |
|
} |
|
|
|
/** |
|
* Extract book metadata from event tags |
|
* Tags: C (collection), T (title), c (chapter), s (section), v (version) |
|
*/ |
|
export function extractBookMetadata(event: { tags: string[][] }): { |
|
type?: string |
|
book?: string |
|
chapter?: string |
|
verse?: string |
|
version?: string |
|
} { |
|
const metadata: any = {} |
|
|
|
for (const [tag, value] of event.tags) { |
|
switch (tag) { |
|
case 'C': // Collection |
|
metadata.type = value |
|
break |
|
case 'T': // Title (book name) |
|
metadata.book = value |
|
break |
|
case 'c': // Chapter |
|
metadata.chapter = value |
|
break |
|
case 's': // Section |
|
// Section might be used for verse or other metadata |
|
// If we don't have verse yet, use section as verse |
|
if (!metadata.verse) { |
|
metadata.verse = value |
|
} |
|
break |
|
case 'v': // Version |
|
metadata.version = value |
|
break |
|
} |
|
} |
|
|
|
return metadata |
|
} |
|
|
|
|