shamela 1.3.3 → 1.3.4
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/README.md +32 -2
- package/dist/content-B60R0uYQ.js +8 -0
- package/dist/content-B60R0uYQ.js.map +1 -0
- package/dist/content-CwjMtCQl.d.ts +54 -0
- package/dist/content.d.ts +2 -0
- package/dist/content.js +1 -0
- package/dist/index.d.ts +4 -270
- package/dist/index.js +7 -13
- package/dist/index.js.map +1 -1
- package/dist/types-C693UiUs.d.ts +226 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +0 -0
- package/package.json +18 -8
package/README.md
CHANGED
|
@@ -30,7 +30,8 @@ A universal TypeScript library for accessing and downloading Maktabah Shamela v4
|
|
|
30
30
|
- [Quick Start](#quick-start)
|
|
31
31
|
- [Standard Node.js](#standard-nodejs)
|
|
32
32
|
- [Next.js / Bundled Environments](#nextjs--bundled-environments)
|
|
33
|
-
- [Browser](#browser)
|
|
33
|
+
- [Browser (Full API)](#browser-full-api)
|
|
34
|
+
- [Browser (Content Utilities Only)](#browser-content-utilities-only)
|
|
34
35
|
- [API Reference](#api-reference)
|
|
35
36
|
- [Configuration](#configuration)
|
|
36
37
|
- [configure](#configure)
|
|
@@ -155,7 +156,7 @@ export async function downloadBookAction(bookId: number) {
|
|
|
155
156
|
|
|
156
157
|
**Important:** Only import `shamela` in server-side code (Server Actions, API Routes, or Server Components). Never import in client components or `layout.tsx`.
|
|
157
158
|
|
|
158
|
-
### Browser
|
|
159
|
+
### Browser (Full API)
|
|
159
160
|
|
|
160
161
|
In browsers, the library automatically uses a CDN-hosted WASM file:
|
|
161
162
|
|
|
@@ -172,6 +173,35 @@ configure({
|
|
|
172
173
|
const book = await getBook(26592);
|
|
173
174
|
```
|
|
174
175
|
|
|
176
|
+
### Browser (Content Utilities Only)
|
|
177
|
+
|
|
178
|
+
If you only need the content processing utilities (sanitization, parsing, etc.) without the database functionality, use the lightweight `shamela/content` export:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import {
|
|
182
|
+
sanitizePageContent,
|
|
183
|
+
splitPageBodyFromFooter,
|
|
184
|
+
removeTagsExceptSpan,
|
|
185
|
+
parseContentRobust,
|
|
186
|
+
} from 'shamela/content';
|
|
187
|
+
|
|
188
|
+
// Process content without loading sql.js (~1.5KB gzipped vs ~900KB)
|
|
189
|
+
const clean = removeTagsExceptSpan(sanitizePageContent(rawContent));
|
|
190
|
+
const [body, footnotes] = splitPageBodyFromFooter(clean);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
This is ideal for:
|
|
194
|
+
- Client-side React/Next.js components
|
|
195
|
+
- Bundled environments where you want to avoid sql.js WASM
|
|
196
|
+
- Processing pre-downloaded book data
|
|
197
|
+
|
|
198
|
+
**Available exports from `shamela/content`:**
|
|
199
|
+
- `parseContentRobust` - Parse HTML into structured lines
|
|
200
|
+
- `sanitizePageContent` - Normalize Arabic text
|
|
201
|
+
- `splitPageBodyFromFooter` - Separate body from footnotes
|
|
202
|
+
- `removeArabicNumericPageMarkers` - Remove page markers
|
|
203
|
+
- `removeTagsExceptSpan` - Strip HTML except spans
|
|
204
|
+
|
|
175
205
|
## API Reference
|
|
176
206
|
|
|
177
207
|
### Configuration
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const e=0,t={"<img[^>]*>>":``,舄:``,"﵀":`رَحِمَهُ ٱللَّٰهُ`,"﵁":`رضي الله عنه`,"﵂":`رَضِيَ ٱللَّٰهُ عَنْهَا`,"﵃":`رَضِيَ اللَّهُ عَنْهُمْ`,"﵄":`رَضِيَ ٱللَّٰهُ عَنْهُمَا`,"﵅":`رَضِيَ اللَّهُ عَنْهُنَّ`,"﵇":`عَلَيْهِ ٱلسَّلَٰمُ`,"﵈":`عَلَيْهِمُ السَّلامُ`,"﵊":`عليه الصلاة والسلام`,"﵌":`صلى الله عليه وآله وسلم`,"﵍":`عَلَيْهِ ٱلسَّلَٰمُ`,"﵎":`تبارك وتعالى`,"﵏":`رَحِمَهُمُ ٱللَّٰهُ`,"﷽":``,"﷿":`عَزَّ وَجَلَّ`},n=/^[)\]\u00BB"”'’.,?!:\u061B\u060C\u061F\u06D4\u2026]+$/,r=e=>{let t=[];for(let r of e){let e=t[t.length-1];e&&n.test(r.text)?e.text+=r.text:t.push(r)}return t},i=e=>e.replace(/\r\n/g,`
|
|
2
|
+
`).replace(/\r/g,`
|
|
3
|
+
`).split(`
|
|
4
|
+
`).map(e=>e.trim()).filter(Boolean),a=e=>i(e).map(e=>({text:e})),o=(e,t)=>{let n=RegExp(`${t}\\s*=\\s*("([^"]*)"|'([^']*)'|([^s>]+))`,`i`),r=e.match(n);if(r)return r[2]??r[3]??r[4]},s=e=>{let t=[],n=/<[^>]+>/g,r=0,i;for(i=n.exec(e);i;){i.index>r&&t.push({type:`text`,value:e.slice(r,i.index)});let a=i[0],s=/^<\//.test(a),c=a.match(/^<\/?\s*([a-zA-Z0-9:-]+)/),l=c?c[1].toLowerCase():``;if(s)t.push({name:l,type:`end`});else{let e={};e.id=o(a,`id`),e[`data-type`]=o(a,`data-type`),t.push({attributes:e,name:l,type:`start`})}r=n.lastIndex,i=n.exec(e)}return r<e.length&&t.push({type:`text`,value:e.slice(r)}),t},c=(e,t)=>{let n=e.trim();return n?t?{id:t,text:n}:{text:n}:null},l=e=>{for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.isTitle&&n.id)return n.id}},u=(e,t)=>{if(!e)return;let n=e.split(`
|
|
5
|
+
`);for(let e=0;e<n.length;e++){if(e>0){let e=c(t.currentText,t.currentId);e&&t.result.push(e),t.currentText=``,t.currentId=l(t.spanStack)||void 0}n[e]&&(t.currentText+=n[e])}},d=(e,t)=>{let n=e.attributes[`data-type`]===`title`,r;n&&(r=(e.attributes.id??``).replace(/^toc-/,``)),t.spanStack.push({id:r,isTitle:n}),n&&r&&!t.currentId&&(t.currentId=r)},f=e=>{if(e=e.replace(/\r\n/g,`
|
|
6
|
+
`).replace(/\r/g,`
|
|
7
|
+
`),!/<span[^>]*>/i.test(e))return r(a(e));let t=s(`<root>${e}</root>`),n={currentId:void 0,currentText:``,result:[],spanStack:[]};for(let e of t)e.type===`text`?u(e.value,n):e.type===`start`&&e.name===`span`?d(e,n):e.type===`end`&&e.name===`span`&&n.spanStack.pop();let i=c(n.currentText,n.currentId);return i&&n.result.push(i),r(n.result).filter(e=>e.text.length>0)},p=Object.entries(t).map(([e,t])=>({regex:new RegExp(e,`g`),replacement:t})),m=e=>{if(e===t)return p;let n=[];for(let t in e)n.push({regex:new RegExp(t,`g`),replacement:e[t]});return n},h=(e,n=t)=>{let r=m(n),i=e;for(let e=0;e<r.length;e++){let{regex:t,replacement:n}=r[e];i=i.replace(t,n)}return i},g=(e,t=`_________`)=>{let n=``,r=e.indexOf(t);return r>=0&&(n=e.slice(r+t.length),e=e.slice(0,r)),[e,n]},_=e=>e.replace(/(?: |\r){0,2}⦗[\u0660-\u0669]+⦘(?: |\r)?/g,` `),v=e=>(e=e.replace(/<a[^>]*>(.*?)<\/a>/gs,`$1`),e=e.replace(/<hadeeth[^>]*>|<\/hadeeth>|<hadeeth-\d+>/gs,``),e),y=e=>e.replace(/<hadeeth-\d+>/gi,`<span class="hadeeth">`).replace(/<\s*\/?\s*hadeeth\s*>/gi,`</span>`);export{h as a,v as i,f as n,g as o,_ as r,e as s,y as t};
|
|
8
|
+
//# sourceMappingURL=content-B60R0uYQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-B60R0uYQ.js","names":["DEFAULT_SANITIZATION_RULES: Record<string, string>","out: Line[]","tokens: Token[]","match: RegExpExecArray | null","attributes: Record<string, string | undefined>","id: string | undefined"],"sources":["../src/utils/constants.ts","../src/content.ts"],"sourcesContent":["/**\n * The default version number for master metadata.\n * @constant {number}\n */\nexport const DEFAULT_MASTER_METADATA_VERSION = 0;\n\n/**\n * Placeholder value used to represent unknown or missing data.\n * @constant {string}\n */\nexport const UNKNOWN_VALUE_PLACEHOLDER = '99999';\n\n/**\n * Default rules to sanitize page content.\n */\nexport const DEFAULT_SANITIZATION_RULES: Record<string, string> = {\n '<img[^>]*>>': '',\n 舄: '',\n '﵀': 'رَحِمَهُ ٱللَّٰهُ',\n '﵁': 'رضي الله عنه',\n '﵂': 'رَضِيَ ٱللَّٰهُ عَنْهَا',\n '﵃': 'رَضِيَ اللَّهُ عَنْهُمْ',\n '﵄': 'رَضِيَ ٱللَّٰهُ عَنْهُمَا',\n '﵅': 'رَضِيَ اللَّهُ عَنْهُنَّ',\n '﵇': 'عَلَيْهِ ٱلسَّلَٰمُ',\n '﵈': 'عَلَيْهِمُ السَّلامُ',\n '﵊': 'عليه الصلاة والسلام',\n '﵌': 'صلى الله عليه وآله وسلم',\n '﵍': 'عَلَيْهِ ٱلسَّلَٰمُ',\n '﵎': 'تبارك وتعالى',\n '﵏': 'رَحِمَهُمُ ٱللَّٰهُ',\n '﷽': '',\n '﷿': 'عَزَّ وَجَلَّ',\n};\n","import { DEFAULT_SANITIZATION_RULES } from './utils/constants';\n\nexport type Line = {\n id?: string;\n text: string;\n};\n\nconst PUNCT_ONLY = /^[)\\]\\u00BB\"”'’.,?!:\\u061B\\u060C\\u061F\\u06D4\\u2026]+$/;\n\n/**\n * Merges punctuation-only lines into the preceding title when appropriate.\n *\n * @param lines - The processed line candidates to normalise\n * @returns A new array where dangling punctuation fragments are appended to titles\n */\nconst mergeDanglingPunctuation = (lines: Line[]): Line[] => {\n const out: Line[] = [];\n for (const item of lines) {\n const last = out[out.length - 1];\n if (last && PUNCT_ONLY.test(item.text)) {\n last.text += item.text;\n } else {\n out.push(item);\n }\n }\n return out;\n};\n\n/**\n * Normalises raw text into discrete line entries.\n *\n * @param text - Raw book content potentially containing inconsistent breaks\n * @returns An array of trimmed line strings with empty entries removed\n */\nconst splitIntoLines = (text: string) => {\n const normalized = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n return normalized\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n};\n\n/**\n * Converts plain text content into {@link Line} objects without title metadata.\n *\n * @param content - The text content to split into line structures\n * @returns A {@link Line} array wrapping each detected sentence fragment\n */\nconst processTextContent = (content: string): Line[] => {\n return splitIntoLines(content).map((line) => ({ text: line }));\n};\n\n/**\n * Extracts an attribute value from the provided HTML tag string.\n *\n * @param tag - Raw HTML tag source\n * @param name - Attribute name to locate\n * @returns The attribute value when found; otherwise undefined\n */\nconst extractAttribute = (tag: string, name: string): string | undefined => {\n const pattern = new RegExp(`${name}\\\\s*=\\\\s*(\"([^\"]*)\"|'([^']*)'|([^s>]+))`, 'i');\n const match = tag.match(pattern);\n if (!match) {\n return undefined;\n }\n return match[2] ?? match[3] ?? match[4];\n};\n\ntype Token =\n | { type: 'text'; value: string }\n | { type: 'start'; name: string; attributes: Record<string, string | undefined> }\n | { type: 'end'; name: string };\n\n/**\n * Breaks the provided HTML fragment into structural tokens.\n *\n * @param html - HTML fragment containing book content markup\n * @returns A token stream describing text and span boundaries\n */\nconst tokenize = (html: string): Token[] => {\n const tokens: Token[] = [];\n const tagRegex = /<[^>]+>/g;\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n match = tagRegex.exec(html);\n\n while (match) {\n if (match.index > lastIndex) {\n tokens.push({ type: 'text', value: html.slice(lastIndex, match.index) });\n }\n\n const raw = match[0];\n const isEnd = /^<\\//.test(raw);\n const nameMatch = raw.match(/^<\\/?\\s*([a-zA-Z0-9:-]+)/);\n const name = nameMatch ? nameMatch[1].toLowerCase() : '';\n\n if (isEnd) {\n tokens.push({ name, type: 'end' });\n } else {\n const attributes: Record<string, string | undefined> = {};\n attributes.id = extractAttribute(raw, 'id');\n attributes['data-type'] = extractAttribute(raw, 'data-type');\n tokens.push({ attributes, name, type: 'start' });\n }\n\n lastIndex = tagRegex.lastIndex;\n match = tagRegex.exec(html);\n }\n\n if (lastIndex < html.length) {\n tokens.push({ type: 'text', value: html.slice(lastIndex) });\n }\n\n return tokens;\n};\n\n/**\n * Pushes the accumulated text as a new line to the result array.\n */\nconst createLine = (text: string, id?: string): Line | null => {\n const trimmed = text.trim();\n if (!trimmed) {\n return null;\n }\n return id ? { id, text: trimmed } : { text: trimmed };\n};\n\n/**\n * Finds the active title ID from the span stack.\n */\nconst getActiveTitleId = (spanStack: Array<{ isTitle: boolean; id?: string }>): string | undefined => {\n for (let i = spanStack.length - 1; i >= 0; i--) {\n const entry = spanStack[i];\n if (entry.isTitle && entry.id) {\n return entry.id;\n }\n }\n};\n\n/**\n * Processes text content by handling line breaks and maintaining title context.\n */\nconst processTextWithLineBreaks = (\n raw: string,\n state: {\n currentText: string;\n currentId?: string;\n result: Line[];\n spanStack: Array<{ isTitle: boolean; id?: string }>;\n },\n) => {\n if (!raw) {\n return;\n }\n\n const parts = raw.split('\\n');\n\n for (let i = 0; i < parts.length; i++) {\n // Push previous line when crossing a line break\n if (i > 0) {\n const line = createLine(state.currentText, state.currentId);\n if (line) {\n state.result.push(line);\n }\n state.currentText = '';\n\n // Preserve title ID if still inside a title span\n const activeTitleId = getActiveTitleId(state.spanStack);\n state.currentId = activeTitleId || undefined;\n }\n\n // Append the text part\n if (parts[i]) {\n state.currentText += parts[i];\n }\n }\n};\n\n/**\n * Handles the start of a span tag, updating the stack and current ID.\n */\nconst handleSpanStart = (\n token: { attributes: Record<string, string | undefined> },\n state: {\n currentId?: string;\n spanStack: Array<{ isTitle: boolean; id?: string }>;\n },\n) => {\n const dataType = token.attributes['data-type'];\n const isTitle = dataType === 'title';\n\n let id: string | undefined;\n if (isTitle) {\n const rawId = token.attributes.id ?? '';\n id = rawId.replace(/^toc-/, '');\n }\n\n state.spanStack.push({ id, isTitle });\n\n // First title span on the current physical line wins\n if (isTitle && id && !state.currentId) {\n state.currentId = id;\n }\n};\n\n/**\n * Parses Shamela HTML content into structured lines while preserving headings.\n *\n * @param content - The raw HTML markup representing a page\n * @returns An array of {@link Line} objects containing text and optional IDs\n */\nexport const parseContentRobust = (content: string): Line[] => {\n // Normalize line endings first\n content = content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n // Fast path when there are no span tags at all\n if (!/<span[^>]*>/i.test(content)) {\n return mergeDanglingPunctuation(processTextContent(content));\n }\n\n const tokens = tokenize(`<root>${content}</root>`);\n const state = {\n currentId: undefined as string | undefined,\n currentText: '',\n result: [] as Line[],\n spanStack: [] as Array<{ isTitle: boolean; id?: string }>,\n };\n\n // Process all tokens\n for (const token of tokens) {\n if (token.type === 'text') {\n processTextWithLineBreaks(token.value, state);\n } else if (token.type === 'start' && token.name === 'span') {\n handleSpanStart(token, state);\n } else if (token.type === 'end' && token.name === 'span') {\n // Closing a span does NOT end the line; trailing text stays on the same line\n state.spanStack.pop();\n }\n }\n\n // Flush any trailing text\n const finalLine = createLine(state.currentText, state.currentId);\n if (finalLine) {\n state.result.push(finalLine);\n }\n\n // Merge punctuation-only lines and drop empties\n return mergeDanglingPunctuation(state.result).filter((line) => line.text.length > 0);\n};\n\nconst DEFAULT_COMPILED_RULES = Object.entries(DEFAULT_SANITIZATION_RULES).map(([pattern, replacement]) => ({\n regex: new RegExp(pattern, 'g'),\n replacement,\n}));\n\n/**\n * Compiles sanitisation rules into RegExp objects for reuse.\n *\n * @param rules - Key/value replacements used during sanitisation\n * @returns A list of compiled regular expression rules\n */\nconst getCompiledRules = (rules: Record<string, string>) => {\n if (rules === DEFAULT_SANITIZATION_RULES) {\n return DEFAULT_COMPILED_RULES;\n }\n\n const compiled = [];\n for (const pattern in rules) {\n compiled.push({\n regex: new RegExp(pattern, 'g'),\n replacement: rules[pattern],\n });\n }\n return compiled;\n};\n\n/**\n * Sanitises page content by applying regex replacement rules.\n *\n * @param text - The text to clean\n * @param rules - Optional custom replacements, defaults to {@link DEFAULT_SANITIZATION_RULES}\n * @returns The sanitised content\n */\nexport const sanitizePageContent = (\n text: string,\n rules: Record<string, string> = DEFAULT_SANITIZATION_RULES,\n): string => {\n const compiledRules = getCompiledRules(rules);\n\n let content = text;\n for (let i = 0; i < compiledRules.length; i++) {\n const { regex, replacement } = compiledRules[i];\n content = content.replace(regex, replacement);\n }\n return content;\n};\n\n/**\n * Splits a page body from its trailing footnotes using a marker string.\n *\n * @param content - Combined body and footnote text\n * @param footnoteMarker - Marker indicating the start of footnotes\n * @returns A tuple containing the page body followed by the footnote section\n */\nexport const splitPageBodyFromFooter = (content: string, footnoteMarker = '_________') => {\n let footnote = '';\n const indexOfFootnote = content.indexOf(footnoteMarker);\n\n if (indexOfFootnote >= 0) {\n footnote = content.slice(indexOfFootnote + footnoteMarker.length);\n content = content.slice(0, indexOfFootnote);\n }\n\n return [content, footnote] as const;\n};\n\n/**\n * Removes Arabic numeral page markers enclosed in turtle ⦗ ⦘ brackets.\n * Replaces the marker along with up to two preceding whitespace characters\n * (space or carriage return) and up to one following whitespace character\n * with a single space.\n *\n * @param text - Text potentially containing page markers\n * @returns The text with numeric markers replaced by a single space\n */\nexport const removeArabicNumericPageMarkers = (text: string) => {\n return text.replace(/(?: |\\r){0,2}⦗[\\u0660-\\u0669]+⦘(?: |\\r)?/g, ' ');\n};\n\n/**\n * Removes anchor and hadeeth tags from the content while preserving spans.\n *\n * @param content - HTML string containing various tags\n * @returns The content with only span tags retained\n */\nexport const removeTagsExceptSpan = (content: string) => {\n // Remove <a> tags and their content, keeping only the text inside\n content = content.replace(/<a[^>]*>(.*?)<\\/a>/gs, '$1');\n\n // Remove <hadeeth> tags (both self-closing, with content, and numbered)\n content = content.replace(/<hadeeth[^>]*>|<\\/hadeeth>|<hadeeth-\\d+>/gs, '');\n\n return content;\n};\n\n/**\n * Normalizes Shamela HTML for CSS styling:\n * - Converts <hadeeth-N> to <span class=\"hadeeth\">\n * - Converts </hadeeth> or standalone <hadeeth> to </span>\n */\nexport const normalizeHtml = (html: string): string => {\n return html.replace(/<hadeeth-\\d+>/gi, '<span class=\"hadeeth\">').replace(/<\\s*\\/?\\s*hadeeth\\s*>/gi, '</span>');\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/content.d.ts
|
|
2
|
+
type Line = {
|
|
3
|
+
id?: string;
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Parses Shamela HTML content into structured lines while preserving headings.
|
|
8
|
+
*
|
|
9
|
+
* @param content - The raw HTML markup representing a page
|
|
10
|
+
* @returns An array of {@link Line} objects containing text and optional IDs
|
|
11
|
+
*/
|
|
12
|
+
declare const parseContentRobust: (content: string) => Line[];
|
|
13
|
+
/**
|
|
14
|
+
* Sanitises page content by applying regex replacement rules.
|
|
15
|
+
*
|
|
16
|
+
* @param text - The text to clean
|
|
17
|
+
* @param rules - Optional custom replacements, defaults to {@link DEFAULT_SANITIZATION_RULES}
|
|
18
|
+
* @returns The sanitised content
|
|
19
|
+
*/
|
|
20
|
+
declare const sanitizePageContent: (text: string, rules?: Record<string, string>) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Splits a page body from its trailing footnotes using a marker string.
|
|
23
|
+
*
|
|
24
|
+
* @param content - Combined body and footnote text
|
|
25
|
+
* @param footnoteMarker - Marker indicating the start of footnotes
|
|
26
|
+
* @returns A tuple containing the page body followed by the footnote section
|
|
27
|
+
*/
|
|
28
|
+
declare const splitPageBodyFromFooter: (content: string, footnoteMarker?: string) => readonly [string, string];
|
|
29
|
+
/**
|
|
30
|
+
* Removes Arabic numeral page markers enclosed in turtle ⦗ ⦘ brackets.
|
|
31
|
+
* Replaces the marker along with up to two preceding whitespace characters
|
|
32
|
+
* (space or carriage return) and up to one following whitespace character
|
|
33
|
+
* with a single space.
|
|
34
|
+
*
|
|
35
|
+
* @param text - Text potentially containing page markers
|
|
36
|
+
* @returns The text with numeric markers replaced by a single space
|
|
37
|
+
*/
|
|
38
|
+
declare const removeArabicNumericPageMarkers: (text: string) => string;
|
|
39
|
+
/**
|
|
40
|
+
* Removes anchor and hadeeth tags from the content while preserving spans.
|
|
41
|
+
*
|
|
42
|
+
* @param content - HTML string containing various tags
|
|
43
|
+
* @returns The content with only span tags retained
|
|
44
|
+
*/
|
|
45
|
+
declare const removeTagsExceptSpan: (content: string) => string;
|
|
46
|
+
/**
|
|
47
|
+
* Normalizes Shamela HTML for CSS styling:
|
|
48
|
+
* - Converts <hadeeth-N> to <span class="hadeeth">
|
|
49
|
+
* - Converts </hadeeth> or standalone <hadeeth> to </span>
|
|
50
|
+
*/
|
|
51
|
+
declare const normalizeHtml: (html: string) => string;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { removeTagsExceptSpan as a, removeArabicNumericPageMarkers as i, normalizeHtml as n, sanitizePageContent as o, parseContentRobust as r, splitPageBodyFromFooter as s, Line as t };
|
|
54
|
+
//# sourceMappingURL=content-CwjMtCQl.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as removeTagsExceptSpan, i as removeArabicNumericPageMarkers, n as normalizeHtml, o as sanitizePageContent, r as parseContentRobust, s as splitPageBodyFromFooter, t as Line } from "./content-CwjMtCQl.js";
|
|
2
|
+
export { Line, normalizeHtml, parseContentRobust, removeArabicNumericPageMarkers, removeTagsExceptSpan, sanitizePageContent, splitPageBodyFromFooter };
|
package/dist/content.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,i as t,n,o as r,r as i,t as a}from"./content-B60R0uYQ.js";export{a as normalizeHtml,n as parseContentRobust,i as removeArabicNumericPageMarkers,t as removeTagsExceptSpan,e as sanitizePageContent,r as splitPageBodyFromFooter};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,228 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { a as removeTagsExceptSpan, i as removeArabicNumericPageMarkers, n as normalizeHtml, o as sanitizePageContent, r as parseContentRobust, s as splitPageBodyFromFooter, t as Line } from "./content-CwjMtCQl.js";
|
|
2
|
+
import { a as DownloadBookOptions, c as GetBookMetadataResponsePayload, d as OutputOptions, f as Page, h as Title, i as Category, l as GetMasterMetadataResponsePayload, m as ShamelaConfigKey, n as Book, o as DownloadMasterOptions, p as ShamelaConfig, r as BookData, s as GetBookMetadataOptions, t as Author, u as MasterData } from "./types-C693UiUs.js";
|
|
2
3
|
|
|
3
|
-
/**
|
|
4
|
-
* A record that can be deleted by patches.
|
|
5
|
-
*/
|
|
6
|
-
type Deletable = {
|
|
7
|
-
/** Indicates if it was deleted in the patch if it is set to '1 */
|
|
8
|
-
is_deleted?: string;
|
|
9
|
-
};
|
|
10
|
-
type Unique = {
|
|
11
|
-
/** Unique identifier */
|
|
12
|
-
id: number;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Database row structure for the author table.
|
|
16
|
-
*/
|
|
17
|
-
type AuthorRow = Deletable & Unique & {
|
|
18
|
-
/** Author biography */
|
|
19
|
-
biography: string;
|
|
20
|
-
/** Death year */
|
|
21
|
-
death_number: string;
|
|
22
|
-
/** The death year as a text */
|
|
23
|
-
death_text: string;
|
|
24
|
-
/** Author name */
|
|
25
|
-
name: string;
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Database row structure for the book table.
|
|
29
|
-
*/
|
|
30
|
-
type BookRow = Deletable & Unique & {
|
|
31
|
-
/** Serialized author ID(s) "2747, 3147" or "513" */
|
|
32
|
-
author: string;
|
|
33
|
-
/** Bibliography information */
|
|
34
|
-
bibliography: string;
|
|
35
|
-
/** Category ID */
|
|
36
|
-
category: string;
|
|
37
|
-
/** Publication date (or 99999 for unavailable) */
|
|
38
|
-
date: string;
|
|
39
|
-
/** Hint or description */
|
|
40
|
-
hint: string;
|
|
41
|
-
/** Major version */
|
|
42
|
-
major_release: string;
|
|
43
|
-
/** Serialized metadata */
|
|
44
|
-
metadata: string;
|
|
45
|
-
/** Minor version */
|
|
46
|
-
minor_release: string;
|
|
47
|
-
/** Book name */
|
|
48
|
-
name: string;
|
|
49
|
-
/** Serialized PDF links */
|
|
50
|
-
pdf_links: string;
|
|
51
|
-
/** Printed flag */
|
|
52
|
-
printed: string;
|
|
53
|
-
/** Book type */
|
|
54
|
-
type: string;
|
|
55
|
-
};
|
|
56
|
-
/**
|
|
57
|
-
* Database row structure for the category table.
|
|
58
|
-
*/
|
|
59
|
-
type CategoryRow = Deletable & Unique & {
|
|
60
|
-
/** Category name */
|
|
61
|
-
name: string;
|
|
62
|
-
/** Category order in the list to show. */
|
|
63
|
-
order: string;
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Database row structure for the page table.
|
|
67
|
-
*/
|
|
68
|
-
type PageRow = Deletable & Unique & {
|
|
69
|
-
/** Page content */
|
|
70
|
-
content: string;
|
|
71
|
-
/** Page number */
|
|
72
|
-
number: string | null;
|
|
73
|
-
/** Page reference */
|
|
74
|
-
page: string | null;
|
|
75
|
-
/** Part number */
|
|
76
|
-
part: string | null;
|
|
77
|
-
/** Additional metadata */
|
|
78
|
-
services: string | null;
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* Database row structure for the title table.
|
|
82
|
-
*/
|
|
83
|
-
type TitleRow = Deletable & Unique & {
|
|
84
|
-
/** Title content */
|
|
85
|
-
content: string;
|
|
86
|
-
/** Page number */
|
|
87
|
-
page: string;
|
|
88
|
-
/** Parent title ID */
|
|
89
|
-
parent: string | null;
|
|
90
|
-
};
|
|
91
|
-
//#endregion
|
|
92
|
-
//#region src/types.d.ts
|
|
93
|
-
/**
|
|
94
|
-
* Represents an author entity.
|
|
95
|
-
*/
|
|
96
|
-
type Author = AuthorRow;
|
|
97
|
-
/**
|
|
98
|
-
* Represents a book entity.
|
|
99
|
-
*/
|
|
100
|
-
type Book = BookRow;
|
|
101
|
-
/**
|
|
102
|
-
* A category for a book.
|
|
103
|
-
*/
|
|
104
|
-
type Category = CategoryRow;
|
|
105
|
-
/**
|
|
106
|
-
* A page in a book.
|
|
107
|
-
*/
|
|
108
|
-
type Page = Pick<PageRow, 'id' | 'content'> & {
|
|
109
|
-
page?: number;
|
|
110
|
-
part?: string;
|
|
111
|
-
number?: string;
|
|
112
|
-
};
|
|
113
|
-
/**
|
|
114
|
-
* A title heading in a book.
|
|
115
|
-
*/
|
|
116
|
-
type Title = Pick<TitleRow, 'id' | 'content'> & {
|
|
117
|
-
page: number;
|
|
118
|
-
parent?: number;
|
|
119
|
-
};
|
|
120
|
-
/**
|
|
121
|
-
* Represents book content data.
|
|
122
|
-
*/
|
|
123
|
-
type BookData = {
|
|
124
|
-
/** Array of pages in the book */
|
|
125
|
-
pages: Page[];
|
|
126
|
-
/** Array of titles/chapters */
|
|
127
|
-
titles: Title[];
|
|
128
|
-
};
|
|
129
|
-
/**
|
|
130
|
-
* Master data structure containing all core entities.
|
|
131
|
-
*/
|
|
132
|
-
type MasterData = {
|
|
133
|
-
/** Array of all authors */
|
|
134
|
-
authors: Author[];
|
|
135
|
-
/** Array of all books */
|
|
136
|
-
books: Book[];
|
|
137
|
-
/** Array of all categories */
|
|
138
|
-
categories: Category[];
|
|
139
|
-
/** Version number for the downloaded master database */
|
|
140
|
-
version: number;
|
|
141
|
-
};
|
|
142
|
-
/**
|
|
143
|
-
* Options for downloading a book.
|
|
144
|
-
*/
|
|
145
|
-
type DownloadBookOptions = {
|
|
146
|
-
/** Optional book metadata */
|
|
147
|
-
bookMetadata?: GetBookMetadataResponsePayload;
|
|
148
|
-
/** Output file configuration */
|
|
149
|
-
outputFile: OutputOptions;
|
|
150
|
-
};
|
|
151
|
-
/**
|
|
152
|
-
* Options for downloading master data.
|
|
153
|
-
*/
|
|
154
|
-
type DownloadMasterOptions = {
|
|
155
|
-
/** Optional master metadata */
|
|
156
|
-
masterMetadata?: GetMasterMetadataResponsePayload;
|
|
157
|
-
/** Output file configuration */
|
|
158
|
-
outputFile: OutputOptions;
|
|
159
|
-
};
|
|
160
|
-
/**
|
|
161
|
-
* Options for getting book metadata.
|
|
162
|
-
*/
|
|
163
|
-
type GetBookMetadataOptions = {
|
|
164
|
-
/** Major version number */
|
|
165
|
-
majorVersion: number;
|
|
166
|
-
/** Minor version number */
|
|
167
|
-
minorVersion: number;
|
|
168
|
-
};
|
|
169
|
-
/**
|
|
170
|
-
* Response payload for book metadata requests.
|
|
171
|
-
*/
|
|
172
|
-
type GetBookMetadataResponsePayload = {
|
|
173
|
-
/** Major release version */
|
|
174
|
-
majorRelease: number;
|
|
175
|
-
/** URL for major release download */
|
|
176
|
-
majorReleaseUrl: string;
|
|
177
|
-
/** Optional minor release version */
|
|
178
|
-
minorRelease?: number;
|
|
179
|
-
/** Optional URL for minor release download */
|
|
180
|
-
minorReleaseUrl?: string;
|
|
181
|
-
};
|
|
182
|
-
/**
|
|
183
|
-
* Response payload for master metadata requests.
|
|
184
|
-
*/
|
|
185
|
-
type GetMasterMetadataResponsePayload = {
|
|
186
|
-
/** Download URL */
|
|
187
|
-
url: string;
|
|
188
|
-
/** Version number */
|
|
189
|
-
version: number;
|
|
190
|
-
};
|
|
191
|
-
type NodeJSOutput = {
|
|
192
|
-
/** Output file path (Node.js only) */
|
|
193
|
-
path: string;
|
|
194
|
-
writer?: never;
|
|
195
|
-
};
|
|
196
|
-
type CustomOutput = {
|
|
197
|
-
/** Custom writer used when path is not provided */
|
|
198
|
-
writer: (payload: string | Uint8Array) => Promise<void> | void;
|
|
199
|
-
path?: undefined;
|
|
200
|
-
};
|
|
201
|
-
/**
|
|
202
|
-
* Output file options.
|
|
203
|
-
*/
|
|
204
|
-
type OutputOptions = NodeJSOutput | CustomOutput;
|
|
205
|
-
/**
|
|
206
|
-
* Runtime configuration for the library.
|
|
207
|
-
*/
|
|
208
|
-
type ShamelaConfig = {
|
|
209
|
-
/** API key used to authenticate against Shamela services */
|
|
210
|
-
apiKey?: string;
|
|
211
|
-
/** Endpoint used for book metadata */
|
|
212
|
-
booksEndpoint?: string;
|
|
213
|
-
/** Endpoint used for master metadata */
|
|
214
|
-
masterPatchEndpoint?: string;
|
|
215
|
-
/** Optional override for the sql.js wasm asset location */
|
|
216
|
-
sqlJsWasmUrl?: string;
|
|
217
|
-
/** Optional custom fetch implementation for environments without a global fetch */
|
|
218
|
-
fetchImplementation?: typeof fetch;
|
|
219
|
-
};
|
|
220
|
-
/**
|
|
221
|
-
* Valid configuration keys.
|
|
222
|
-
*/
|
|
223
|
-
type ShamelaConfigKey = keyof ShamelaConfig;
|
|
224
|
-
//#endregion
|
|
225
4
|
//#region src/api.d.ts
|
|
5
|
+
|
|
226
6
|
/**
|
|
227
7
|
* Retrieves metadata for a specific book from the Shamela API.
|
|
228
8
|
*
|
|
@@ -399,51 +179,5 @@ declare const configure: (config: ConfigureOptions) => void;
|
|
|
399
179
|
*/
|
|
400
180
|
declare const resetConfig: () => void;
|
|
401
181
|
//#endregion
|
|
402
|
-
|
|
403
|
-
type Line = {
|
|
404
|
-
id?: string;
|
|
405
|
-
text: string;
|
|
406
|
-
};
|
|
407
|
-
/**
|
|
408
|
-
* Parses Shamela HTML content into structured lines while preserving headings.
|
|
409
|
-
*
|
|
410
|
-
* @param content - The raw HTML markup representing a page
|
|
411
|
-
* @returns An array of {@link Line} objects containing text and optional IDs
|
|
412
|
-
*/
|
|
413
|
-
declare const parseContentRobust: (content: string) => Line[];
|
|
414
|
-
/**
|
|
415
|
-
* Sanitises page content by applying regex replacement rules.
|
|
416
|
-
*
|
|
417
|
-
* @param text - The text to clean
|
|
418
|
-
* @param rules - Optional custom replacements, defaults to {@link DEFAULT_SANITIZATION_RULES}
|
|
419
|
-
* @returns The sanitised content
|
|
420
|
-
*/
|
|
421
|
-
declare const sanitizePageContent: (text: string, rules?: Record<string, string>) => string;
|
|
422
|
-
/**
|
|
423
|
-
* Splits a page body from its trailing footnotes using a marker string.
|
|
424
|
-
*
|
|
425
|
-
* @param content - Combined body and footnote text
|
|
426
|
-
* @param footnoteMarker - Marker indicating the start of footnotes
|
|
427
|
-
* @returns A tuple containing the page body followed by the footnote section
|
|
428
|
-
*/
|
|
429
|
-
declare const splitPageBodyFromFooter: (content: string, footnoteMarker?: string) => readonly [string, string];
|
|
430
|
-
/**
|
|
431
|
-
* Removes Arabic numeral page markers enclosed in turtle ⦗ ⦘ brackets.
|
|
432
|
-
* Replaces the marker along with up to two preceding whitespace characters
|
|
433
|
-
* (space or carriage return) and up to one following whitespace character
|
|
434
|
-
* with a single space.
|
|
435
|
-
*
|
|
436
|
-
* @param text - Text potentially containing page markers
|
|
437
|
-
* @returns The text with numeric markers replaced by a single space
|
|
438
|
-
*/
|
|
439
|
-
declare const removeArabicNumericPageMarkers: (text: string) => string;
|
|
440
|
-
/**
|
|
441
|
-
* Removes anchor and hadeeth tags from the content while preserving spans.
|
|
442
|
-
*
|
|
443
|
-
* @param content - HTML string containing various tags
|
|
444
|
-
* @returns The content with only span tags retained
|
|
445
|
-
*/
|
|
446
|
-
declare const removeTagsExceptSpan: (content: string) => string;
|
|
447
|
-
//#endregion
|
|
448
|
-
export { Author, Book, BookData, Category, type ConfigureOptions, DownloadBookOptions, DownloadMasterOptions, GetBookMetadataOptions, GetBookMetadataResponsePayload, GetMasterMetadataResponsePayload, Line, type Logger, MasterData, OutputOptions, Page, ShamelaConfig, ShamelaConfigKey, Title, configure, downloadBook, downloadMasterDatabase, getBook, getBookMetadata, getCoverUrl, getMaster, getMasterMetadata, parseContentRobust, removeArabicNumericPageMarkers, removeTagsExceptSpan, resetConfig, sanitizePageContent, splitPageBodyFromFooter };
|
|
182
|
+
export { Author, Book, BookData, Category, type ConfigureOptions, DownloadBookOptions, DownloadMasterOptions, GetBookMetadataOptions, GetBookMetadataResponsePayload, GetMasterMetadataResponsePayload, Line, type Logger, MasterData, OutputOptions, Page, ShamelaConfig, ShamelaConfigKey, Title, configure, downloadBook, downloadMasterDatabase, getBook, getBookMetadata, getCoverUrl, getMaster, getMasterMetadata, normalizeHtml, parseContentRobust, removeArabicNumericPageMarkers, removeTagsExceptSpan, resetConfig, sanitizePageContent, splitPageBodyFromFooter };
|
|
449
183
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e from"sql.js";import{unzipSync as
|
|
1
|
+
import{a as e,i as t,n,o as r,r as i,s as a,t as o}from"./content-B60R0uYQ.js";import"./types.js";import s from"sql.js";import{unzipSync as ee}from"fflate";var c=(e=>typeof require<`u`?require:typeof Proxy<`u`?new Proxy(e,{get:(e,t)=>(typeof require<`u`?require:e)[t]}):e)(function(e){if(typeof require<`u`)return require.apply(this,arguments);throw Error('Calling `require` for "'+e+"\" in an environment that doesn't expose the `require` function.")});const l=Object.freeze({debug:()=>{},error:()=>{},info:()=>{},warn:()=>{}});let u=l;const te=e=>{if(!e){u=l;return}let t=[`debug`,`error`,`info`,`warn`].find(t=>typeof e[t]!=`function`);if(t)throw Error(`Logger must implement debug, error, info, and warn methods. Missing: ${String(t)}`);u=e},ne=()=>u,re=()=>{u=l};var d=new Proxy({},{get:(e,t)=>{let n=ne(),r=n[t];return typeof r==`function`?(...e)=>r.apply(n,e):r}});let f={};const p={apiKey:`SHAMELA_API_KEY`,booksEndpoint:`SHAMELA_API_BOOKS_ENDPOINT`,masterPatchEndpoint:`SHAMELA_API_MASTER_PATCH_ENDPOINT`,sqlJsWasmUrl:`SHAMELA_SQLJS_WASM_URL`},m=typeof process<`u`&&!!process?.env,h=e=>{let t=f[e];if(t!==void 0)return t;let n=p[e];if(m)return process.env[n]},ie=e=>{let{logger:t,...n}=e;`logger`in e&&te(t),f={...f,...n}},g=e=>e===`fetchImplementation`?f.fetchImplementation:h(e),_=()=>({apiKey:h(`apiKey`),booksEndpoint:h(`booksEndpoint`),fetchImplementation:f.fetchImplementation,masterPatchEndpoint:h(`masterPatchEndpoint`),sqlJsWasmUrl:h(`sqlJsWasmUrl`)}),v=e=>{if(e===`fetchImplementation`)throw Error(`fetchImplementation must be provided via configure().`);let t=g(e);if(!t)throw Error(`${p[e]} environment variable not set`);return t},ae=()=>{f={},re()};let y=function(e){return e.Authors=`author`,e.Books=`book`,e.Categories=`category`,e.Page=`page`,e.Title=`title`,e}({});const b=(e,t)=>e.query(`PRAGMA table_info(${t})`).all(),x=(e,t)=>!!e.query(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?1`).get(t),S=(e,t)=>x(e,t)?e.query(`SELECT * FROM ${t}`).all():[],C=e=>String(e.is_deleted)===`1`,w=(e,t,n)=>{let r={};for(let i of n){if(i===`id`){r.id=(t??e)?.id??null;continue}if(t&&i in t){let e=t[i];if(e!==`#`&&e!=null){r[i]=e;continue}}if(e&&i in e){r[i]=e[i];continue}r[i]=null}return r},oe=(e,t,n)=>{let r=new Set,i=new Map;for(let t of e)r.add(String(t.id));for(let e of t)i.set(String(e.id),e);let a=[];for(let t of e){let e=i.get(String(t.id));e&&C(e)||a.push(w(t,e,n))}for(let e of t){let t=String(e.id);r.has(t)||C(e)||a.push(w(void 0,e,n))}return a},se=(e,t,n,r)=>{if(r.length===0)return;let i=n.map(()=>`?`).join(`,`),a=e.prepare(`INSERT INTO ${t} (${n.join(`,`)}) VALUES (${i})`);r.forEach(e=>{let t=n.map(t=>t in e?e[t]:null);a.run(...t)}),a.finalize()},ce=(e,t,n)=>{let r=t.query(`SELECT sql FROM sqlite_master WHERE type='table' AND name = ?1`).get(n);return r?.sql?(e.run(`DROP TABLE IF EXISTS ${n}`),e.run(r.sql),!0):(d.warn(`${n} table definition missing in source database`),!1)},T=(e,t,n,r)=>{if(!x(t,r)){d.warn(`${r} table missing in source database`);return}if(!ce(e,t,r))return;let i=b(t,r),a=n&&x(n,r)?b(n,r):[],o=i.map(e=>e.name);for(let t of a)if(!o.includes(t.name)){let n=t.type&&t.type.length>0?t.type:`TEXT`;e.run(`ALTER TABLE ${r} ADD COLUMN ${t.name} ${n}`),o.push(t.name)}se(e,r,o,oe(S(t,r),n?S(n,r):[],o))},le=(e,t,n)=>{e.transaction(()=>{T(e,t,n,y.Page),T(e,t,n,y.Title)})()},E=(e,t)=>{e.transaction(()=>{T(e,t,null,y.Page),T(e,t,null,y.Title)})()},D=e=>{e.run(`CREATE TABLE ${y.Page} (
|
|
2
2
|
id INTEGER,
|
|
3
3
|
content TEXT,
|
|
4
4
|
part TEXT,
|
|
@@ -6,21 +6,21 @@ import e from"sql.js";import{unzipSync as t}from"fflate";var n=(e=>typeof requir
|
|
|
6
6
|
number TEXT,
|
|
7
7
|
services TEXT,
|
|
8
8
|
is_deleted TEXT
|
|
9
|
-
)`),e.run(`CREATE TABLE ${
|
|
9
|
+
)`),e.run(`CREATE TABLE ${y.Title} (
|
|
10
10
|
id INTEGER,
|
|
11
11
|
content TEXT,
|
|
12
12
|
page INTEGER,
|
|
13
13
|
parent INTEGER,
|
|
14
14
|
is_deleted TEXT
|
|
15
|
-
)`)},
|
|
16
|
-
`);throw Error(e)}}else
|
|
15
|
+
)`)},ue=e=>e.query(`SELECT * FROM ${y.Page}`).all(),de=e=>e.query(`SELECT * FROM ${y.Title}`).all(),O=e=>({pages:ue(e),titles:de(e)}),k=e=>{try{return c(`node:fs`).existsSync(e)}catch{return!1}},fe=()=>{if(c!==void 0&&c.resolve!==void 0)try{let e=c.resolve(`sql.js`),t=c(`node:path`),n=t.dirname(e),r=t.join(n,`dist`,`sql-wasm.wasm`);if(k(r))return r}catch{}if(typeof process<`u`&&process.cwd)try{let e=c(`node:path`),t=process.cwd(),n=[e.join(t,`node_modules`,`sql.js`,`dist`,`sql-wasm.wasm`),e.join(t,`..`,`node_modules`,`sql.js`,`dist`,`sql-wasm.wasm`),e.join(t,`../..`,`node_modules`,`sql.js`,`dist`,`sql-wasm.wasm`),e.join(t,`.next`,`server`,`node_modules`,`sql.js`,`dist`,`sql-wasm.wasm`)];for(let e of n)if(k(e))return e}catch{}if(c!==void 0&&c.resolve!==void 0&&c.resolve.paths)try{let e=c(`node:path`),t=c.resolve.paths(`sql.js`)||[];for(let n of t){let t=e.join(n,`sql.js`,`dist`,`sql-wasm.wasm`);if(k(t))return t}}catch{}try{if(import.meta.url){let e=new URL(`../../node_modules/sql.js/dist/sql-wasm.wasm`,import.meta.url),t=decodeURIComponent(e.pathname),n=process.platform===`win32`&&t.startsWith(`/`)?t.slice(1):t;if(k(n))return n}}catch{}return null};var pe=class{constructor(e){this.statement=e}run=(...e)=>{e.length>0&&this.statement.bind(e),this.statement.step(),this.statement.reset()};finalize=()=>{this.statement.free()}},A=class{constructor(e){this.db=e}run=(e,t=[])=>{this.db.run(e,t)};prepare=e=>new pe(this.db.prepare(e));query=e=>({all:(...t)=>this.all(e,t),get:(...t)=>this.get(e,t)});transaction=e=>()=>{this.db.run(`BEGIN TRANSACTION`);try{e(),this.db.run(`COMMIT`)}catch(e){throw this.db.run(`ROLLBACK`),e}};close=()=>{this.db.close()};export=()=>this.db.export();all=(e,t)=>{let n=this.db.prepare(e);try{t.length>0&&n.bind(t);let e=[];for(;n.step();)e.push(n.getAsObject());return e}finally{n.free()}};get=(e,t)=>this.all(e,t)[0]};let j=null,M=null;const me=typeof process<`u`&&!!process?.versions?.node,he=()=>{if(!M){let e=g(`sqlJsWasmUrl`);if(e)M=e;else if(me){let e=fe();if(e)M=e;else{let e=[`Unable to automatically locate sql-wasm.wasm file.`,`This can happen in bundled environments (Next.js, webpack, etc.).`,``,`Quick fix - add this to your code before using shamela:`,``,` import { configure, createNodeConfig } from "shamela";`,` configure(createNodeConfig({`,` apiKey: process.env.SHAMELA_API_KEY,`,` booksEndpoint: process.env.SHAMELA_BOOKS_ENDPOINT,`,` masterPatchEndpoint: process.env.SHAMELA_MASTER_ENDPOINT,`,` }));`,``,`Or manually specify the path:`,``,` import { configure } from "shamela";`,` import { join } from "node:path";`,` configure({`,` sqlJsWasmUrl: join(process.cwd(), "node_modules", "sql.js", "dist", "sql-wasm.wasm")`,` });`].join(`
|
|
16
|
+
`);throw Error(e)}}else M=`https://cdn.jsdelivr.net/npm/sql.js@1.13.0/dist/sql-wasm.wasm`}return M},N=()=>(j||=s({locateFile:()=>he()}),j),P=async()=>new A(new(await(N())).Database),F=async e=>new A(new(await(N())).Database(e)),I=(e,t,n)=>{let r=t.query(`SELECT sql FROM sqlite_master WHERE type='table' AND name = ?1`).get(n);if(!r?.sql)throw Error(`Missing table definition for ${n} in source database`);e.run(`DROP TABLE IF EXISTS ${n}`),e.run(r.sql)},L=async(e,t)=>{let n={author:y.Authors,book:y.Books,category:y.Categories},r={};for(let e of t){let t=n[(e.name.split(`/`).pop()?.split(`\\`).pop()??e.name).replace(/\.(sqlite|db)$/i,``).toLowerCase()];t&&(r[t]=await F(e.data))}try{let t=Object.entries(r);e.transaction(()=>{for(let[n,r]of t){I(e,r,n);let t=r.query(`PRAGMA table_info(${n})`).all().map(e=>e.name);if(t.length===0)continue;let i=r.query(`SELECT * FROM ${n}`).all();if(i.length===0)continue;let a=t.map(()=>`?`).join(`,`),o=t.map(e=>e===`order`?`"order"`:e),s=e.prepare(`INSERT INTO ${n} (${o.join(`,`)}) VALUES (${a})`);try{for(let e of i){let n=t.map(t=>t in e?e[t]:null);s.run(...n)}}finally{s.finalize()}}})()}finally{Object.values(r).forEach(e=>e?.close())}},R=(e,t,n)=>{e.run(`DROP VIEW IF EXISTS ${t}`),e.run(`CREATE VIEW ${t} AS SELECT * FROM ${n}`)},ge=e=>{e.run(`CREATE TABLE ${y.Authors} (
|
|
17
17
|
id INTEGER,
|
|
18
18
|
is_deleted TEXT,
|
|
19
19
|
name TEXT,
|
|
20
20
|
biography TEXT,
|
|
21
21
|
death_text TEXT,
|
|
22
22
|
death_number TEXT
|
|
23
|
-
)`),e.run(`CREATE TABLE ${
|
|
23
|
+
)`),e.run(`CREATE TABLE ${y.Books} (
|
|
24
24
|
id INTEGER,
|
|
25
25
|
name TEXT,
|
|
26
26
|
is_deleted TEXT,
|
|
@@ -35,16 +35,10 @@ import e from"sql.js";import{unzipSync as t}from"fflate";var n=(e=>typeof requir
|
|
|
35
35
|
hint TEXT,
|
|
36
36
|
pdf_links TEXT,
|
|
37
37
|
metadata TEXT
|
|
38
|
-
)`),e.run(`CREATE TABLE ${
|
|
38
|
+
)`),e.run(`CREATE TABLE ${y.Categories} (
|
|
39
39
|
id INTEGER,
|
|
40
40
|
is_deleted TEXT,
|
|
41
41
|
"order" TEXT,
|
|
42
42
|
name TEXT
|
|
43
|
-
)`),
|
|
44
|
-
`).replace(/\r/g,`
|
|
45
|
-
`).split(`
|
|
46
|
-
`).map(e=>e.trim()).filter(Boolean),Me=e=>je(e).map(e=>({text:e})),Z=(e,t)=>{let n=RegExp(`${t}\\s*=\\s*("([^"]*)"|'([^']*)'|([^s>]+))`,`i`),r=e.match(n);if(r)return r[2]??r[3]??r[4]},Ne=e=>{let t=[],n=/<[^>]+>/g,r=0,i;for(i=n.exec(e);i;){i.index>r&&t.push({type:`text`,value:e.slice(r,i.index)});let a=i[0],o=/^<\//.test(a),s=a.match(/^<\/?\s*([a-zA-Z0-9:-]+)/),c=s?s[1].toLowerCase():``;if(o)t.push({name:c,type:`end`});else{let e={};e.id=Z(a,`id`),e[`data-type`]=Z(a,`data-type`),t.push({attributes:e,name:c,type:`start`})}r=n.lastIndex,i=n.exec(e)}return r<e.length&&t.push({type:`text`,value:e.slice(r)}),t},Q=(e,t)=>{let n=e.trim();return n?t?{id:t,text:n}:{text:n}:null},Pe=e=>{for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.isTitle&&n.id)return n.id}},Fe=(e,t)=>{if(!e)return;let n=e.split(`
|
|
47
|
-
`);for(let e=0;e<n.length;e++){if(e>0){let e=Q(t.currentText,t.currentId);e&&t.result.push(e),t.currentText=``,t.currentId=Pe(t.spanStack)||void 0}n[e]&&(t.currentText+=n[e])}},Ie=(e,t)=>{let n=e.attributes[`data-type`]===`title`,r;n&&(r=(e.attributes.id??``).replace(/^toc-/,``)),t.spanStack.push({id:r,isTitle:n}),n&&r&&!t.currentId&&(t.currentId=r)},Le=e=>{if(e=e.replace(/\r\n/g,`
|
|
48
|
-
`).replace(/\r/g,`
|
|
49
|
-
`),!/<span[^>]*>/i.test(e))return X(Me(e));let t=Ne(`<root>${e}</root>`),n={currentId:void 0,currentText:``,result:[],spanStack:[]};for(let e of t)e.type===`text`?Fe(e.value,n):e.type===`start`&&e.name===`span`?Ie(e,n):e.type===`end`&&e.name===`span`&&n.spanStack.pop();let r=Q(n.currentText,n.currentId);return r&&n.result.push(r),X(n.result).filter(e=>e.text.length>0)},$=Object.entries(I).map(([e,t])=>({regex:new RegExp(e,`g`),replacement:t})),Re=e=>{if(e===I)return $;let t=[];for(let n in e)t.push({regex:new RegExp(n,`g`),replacement:e[n]});return t},ze=(e,t=I)=>{let n=Re(t),r=e;for(let e=0;e<n.length;e++){let{regex:t,replacement:i}=n[e];r=r.replace(t,i)}return r},Be=(e,t=`_________`)=>{let n=``,r=e.lastIndexOf(t);return r>=0&&(n=e.slice(r+t.length),e=e.slice(0,r)),[e,n]},Ve=e=>e.replace(/(?: |\r){0,2}⦗[\u0660-\u0669]+⦘(?: |\r)?/g,` `),He=e=>(e=e.replace(/<a[^>]*>(.*?)<\/a>/gs,`$1`),e=e.replace(/<hadeeth[^>]*>|<\/hadeeth>|<hadeeth-\d+>/gs,``),e);export{te as configure,Te as downloadBook,De as downloadMasterDatabase,Oe as getBook,J as getBookMetadata,Ee as getCoverUrl,ke as getMaster,Y as getMasterMetadata,Le as parseContentRobust,Ve as removeArabicNumericPageMarkers,He as removeTagsExceptSpan,ne as resetConfig,ze as sanitizePageContent,Be as splitPageBodyFromFooter};
|
|
43
|
+
)`),R(e,`authors`,y.Authors),R(e,`books`,y.Books),R(e,`categories`,y.Categories)},_e=e=>e.query(`SELECT * FROM ${y.Authors}`).all(),ve=e=>e.query(`SELECT * FROM ${y.Books}`).all(),ye=e=>e.query(`SELECT * FROM ${y.Categories}`).all(),z=(e,t)=>({authors:_e(e),books:ve(e),categories:ye(e),version:t}),B=(e,t=[`api_key`,`token`,`password`,`secret`,`auth`])=>{let n=typeof e==`string`?new URL(e):new URL(e.toString());return t.forEach(e=>{let t=n.searchParams.get(e);if(t&&t.length>6){let r=`${t.slice(0,3)}***${t.slice(-3)}`;n.searchParams.set(e,r)}else t&&n.searchParams.set(e,`***`)}),n.toString()},be=e=>({content:e.content,id:e.id,...e.number&&{number:e.number},...e.page&&{page:Number(e.page)},...e.part&&{part:e.part}}),xe=e=>{let t=Number(e.parent);return{content:e.content,id:e.id,page:Number(e.page),...t&&{parent:t}}},V=e=>{let t=new URL(e);return t.protocol=`https`,t.toString()},H=e=>/\.(sqlite|db)$/i.test(e.name),U=e=>e.find(H),W=e=>{let t=/\.([^.]+)$/.exec(e);return t?`.${t[1].toLowerCase()}`:``},G=(e,t,n=!0)=>{let r=new URL(e),i=new URLSearchParams;return Object.entries(t).forEach(([e,t])=>{i.append(e,t.toString())}),n&&i.append(`api_key`,v(`apiKey`)),r.search=i.toString(),r},K=async(e,t={})=>{let n=typeof e==`string`?e:e.toString(),r=await(t.fetchImpl??_().fetchImplementation??fetch)(n);if(!r.ok)throw Error(`Error making request: ${r.status} ${r.statusText}`);if((r.headers.get(`content-type`)??``).includes(`application/json`))return await r.json();let i=await r.arrayBuffer();return new Uint8Array(i)},Se=typeof process<`u`&&!!process?.versions?.node,Ce=async()=>{if(!Se)throw Error(`File system operations are only supported in Node.js environments`);return import(`node:fs/promises`)},we=async e=>{let[t,n]=await Promise.all([Ce(),import(`node:path`)]),r=n.dirname(e);return await t.mkdir(r,{recursive:!0}),t},q=async e=>{let t=await K(e),n=t instanceof Uint8Array?t.length:t&&typeof t.byteLength==`number`?t.byteLength:0;return d.debug(`unzipFromUrl:bytes`,n),new Promise((e,n)=>{let r=t instanceof Uint8Array?t:new Uint8Array(t);try{let t=ee(r),n=Object.entries(t).map(([e,t])=>({data:t,name:e}));d.debug(`unzipFromUrl:entries`,n.map(e=>e.name)),e(n)}catch(e){n(Error(`Error processing URL: ${e.message}`))}})},J=async(e,t)=>{if(e.writer){await e.writer(t);return}if(!e.path)throw Error(`Output options must include either a writer or a path`);let n=await we(e.path);typeof t==`string`?await n.writeFile(e.path,t,`utf-8`):await n.writeFile(e.path,t)},Te=[`author.sqlite`,`book.sqlite`,`category.sqlite`],Y=()=>{let{apiKey:e,booksEndpoint:t,masterPatchEndpoint:n}=_(),r=[[`apiKey`,e],[`booksEndpoint`,t],[`masterPatchEndpoint`,n]].filter(([,e])=>!e).map(([e])=>e);if(r.length)throw Error(`${r.join(`, `)} environment variables not set`)},Ee=e=>{let t=new Set(e.map(e=>e.match(/[^\\/]+$/)?.[0]??e).map(e=>e.toLowerCase()));return Te.every(e=>t.has(e.toLowerCase()))},X=async(e,t)=>{d.info(`Setting up book database for ${e}`);let n=t||await Q(e),r=n.minorReleaseUrl?q(n.minorReleaseUrl):Promise.resolve([]),[i,a]=await Promise.all([q(n.majorReleaseUrl),r]),o=U(i);if(!o)throw Error(`Unable to locate book database in archive`);let s=await P();try{d.info(`Creating tables`),D(s);let e=await F(o.data);try{let t=U(a);if(t){d.info(`Applying patches from ${t.name} to ${o.name}`);let n=await F(t.data);try{le(s,e,n)}finally{n.close()}}else d.info(`Copying table data from ${o.name}`),E(s,e)}finally{e.close()}return{cleanup:async()=>{s.close()},client:s}}catch(e){throw s.close(),e}},Z=async e=>{d.info(`Setting up master database`);let t=e||await $(a);d.info(`Downloading master database ${t.version} from: ${B(t.url)}`);let n=await q(V(t.url));if(d.debug?.(`sourceTables downloaded: ${n.map(e=>e.name).toString()}`),!Ee(n.map(e=>e.name)))throw d.error(`Some source tables were not found: ${n.map(e=>e.name).toString()}`),Error(`Expected tables not found!`);let r=await P();try{return d.info(`Creating master tables`),ge(r),d.info(`Copying data to master table`),await L(r,n.filter(H)),{cleanup:async()=>{r.close()},client:r,version:t.version}}catch(e){throw r.close(),e}},Q=async(e,t)=>{Y();let n=G(`${v(`booksEndpoint`)}/${e}`,{major_release:(t?.majorVersion||0).toString(),minor_release:(t?.minorVersion||0).toString()});d.info(`Fetching shamela.ws book link: ${B(n)}`);try{let e=await K(n);return{majorRelease:e.major_release,majorReleaseUrl:V(e.major_release_url),...e.minor_release_url&&{minorReleaseUrl:V(e.minor_release_url)},...e.minor_release_url&&{minorRelease:e.minor_release}}}catch(e){throw Error(`Error fetching book metadata: ${e.message}`)}},De=async(e,t)=>{if(d.info(`downloadBook ${e} ${JSON.stringify(t)}`),!t.outputFile.path)throw Error(`outputFile.path must be provided to determine output format`);let n=W(t.outputFile.path).toLowerCase(),{client:r,cleanup:i}=await X(e,t?.bookMetadata);try{if(n===`.json`){let e=await O(r);await J(t.outputFile,JSON.stringify(e,null,2))}else if(n===`.db`||n===`.sqlite`){let e=r.export();await J(t.outputFile,e)}else throw Error(`Unsupported output extension: ${n}`)}finally{await i()}return t.outputFile.path},$=async(e=0)=>{Y();let t=G(v(`masterPatchEndpoint`),{version:e.toString()});d.info(`Fetching shamela.ws master database patch link: ${B(t)}`);try{let e=await K(t);return{url:e.patch_url,version:e.version}}catch(e){throw Error(`Error fetching master patch: ${e.message}`)}},Oe=e=>{let t=v(`masterPatchEndpoint`),{origin:n}=new URL(t);return`${n}/covers/${e}.jpg`},ke=async e=>{if(d.info(`downloadMasterDatabase ${JSON.stringify(e)}`),!e.outputFile.path)throw Error(`outputFile.path must be provided to determine output format`);let t=W(e.outputFile.path),{client:n,cleanup:r,version:i}=await Z(e.masterMetadata);try{if(t===`.json`){let t=z(n,i);await J(e.outputFile,JSON.stringify(t,null,2))}else if(t===`.db`||t===`.sqlite`)await J(e.outputFile,n.export());else throw Error(`Unsupported output extension: ${t}`)}finally{await r()}return e.outputFile.path},Ae=async e=>{d.info(`getBook ${e}`);let{client:t,cleanup:n}=await X(e);try{let e=await O(t);return{pages:e.pages.map(be),titles:e.titles.map(xe)}}finally{await n()}},je=async()=>{d.info(`getMaster`);let{client:e,cleanup:t,version:n}=await Z();try{return z(e,n)}finally{await t()}};export{ie as configure,De as downloadBook,ke as downloadMasterDatabase,Ae as getBook,Q as getBookMetadata,Oe as getCoverUrl,je as getMaster,$ as getMasterMetadata,o as normalizeHtml,n as parseContentRobust,i as removeArabicNumericPageMarkers,t as removeTagsExceptSpan,ae as resetConfig,e as sanitizePageContent,r as splitPageBodyFromFooter};
|
|
50
44
|
//# sourceMappingURL=index.js.map
|