@vcgstudiosy-beep/dlof 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/bin/dlof.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +237 -0
- package/dist/dlof/parseDlof.d.ts +6 -0
- package/dist/dlof/parseDlof.js +181 -0
- package/dist/dlof/serializeDlof.d.ts +3 -0
- package/dist/dlof/serializeDlof.js +176 -0
- package/dist/dlof/validate.d.ts +11 -0
- package/dist/dlof/validate.js +76 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +47 -0
- package/dist/loop/loopUtils.d.ts +16 -0
- package/dist/loop/loopUtils.js +66 -0
- package/dist/packages/dlofSeries.d.ts +9 -0
- package/dist/packages/dlofSeries.js +60 -0
- package/dist/packages/dlofpkg.d.ts +10 -0
- package/dist/packages/dlofpkg.js +72 -0
- package/dist/player/renderPlayerHtml.d.ts +13 -0
- package/dist/player/renderPlayerHtml.js +235 -0
- package/dist/types.d.ts +141 -0
- package/dist/types.js +6 -0
- package/dist/viewer/renderContent.d.ts +3 -0
- package/dist/viewer/renderContent.js +77 -0
- package/dist/viewer/renderViewerHtml.d.ts +11 -0
- package/dist/viewer/renderViewerHtml.js +46 -0
- package/dist/viewer/theme.d.ts +13 -0
- package/dist/viewer/theme.js +102 -0
- package/dist/xml/parser.d.ts +22 -0
- package/dist/xml/parser.js +199 -0
- package/dist/xml/serializer.d.ts +11 -0
- package/dist/xml/serializer.js +46 -0
- package/dist/zip/crc32.d.ts +2 -0
- package/dist/zip/crc32.js +22 -0
- package/dist/zip/zipReader.d.ts +14 -0
- package/dist/zip/zipReader.js +109 -0
- package/dist/zip/zipWriter.d.ts +9 -0
- package/dist/zip/zipWriter.js +106 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Droy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# dlof
|
|
2
|
+
|
|
3
|
+
حزمة npm احترافية كاملة لصيغة **DLoF** (Document Loop Format): مكتبة برمجية + أداة سطر أوامر
|
|
4
|
+
لتحليل وإنشاء ملفات `.dlof`، حزم `.dlofpkg` و `.dlofSeries`، بالإضافة إلى **عارض** و **مشغّل**
|
|
5
|
+
HTML تفاعليَّين يعملان بلا أي اعتمادية خارجية (Zero Dependencies) — يستخدمان فقط وحدات Node
|
|
6
|
+
المدمجة (`zlib`, `fs`, `path`).
|
|
7
|
+
|
|
8
|
+
## المزايا
|
|
9
|
+
|
|
10
|
+
- 📄 **تحليل/إنشاء `.dlof`**: قارئ وكاتب XML مخصّص للصيغة (`parseDlof` / `serializeDlof`)، بلا أي اعتماد خارجي.
|
|
11
|
+
- 📦 **`.dlofpkg`**: قراءة وكتابة حزم الملف المفرد (`package.dlof` + `meta.json` + `attachments/`).
|
|
12
|
+
- 🎞️ **`.dlofSeries`**: قراءة وكتابة حزم السلاسل الكاملة، مع حلّ الحلقة (`loopLinks`) تلقائياً.
|
|
13
|
+
- ✅ **تحقق (`validateDlof`)**: يطبّق القواعد الإلزامية في `docs/SPECIFICATION.md`.
|
|
14
|
+
- 🖼️ **عارض `dlofpkg`** (`renderViewerHtml`): يبني صفحة HTML واحدة مستقلة تعرض المحتوى، المرفقات، والتصميم المخصّص (template)، بدعم RTL كامل.
|
|
15
|
+
- ▶️ **مشغّل `dlof`** (`renderPlayerHtml`): تطبيق HTML/JS تفاعلي واحد لسلسلة/حلقة كاملة — تصفّح جانبي، أزرار التالي/السابق تتبع `loopLinks`، تشغيل الوسائط (`mediaRef`) بشريط تقدّم قابل للسحب، وحفظ آخر موضع تصفح في `localStorage`.
|
|
16
|
+
- 🛠️ **CLI باسم `dlof`**: أوامر جاهزة للاستخدام من الطرفية.
|
|
17
|
+
- 🗜️ **قارئ/كاتب ZIP خاص بلا اعتماديات** (`readZip` / `writeZip`) مبني فوق `zlib` المدمجة في Node — متوافق تماماً مع أدوات `zip`/`unzip` القياسية.
|
|
18
|
+
|
|
19
|
+
## التثبيت
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g dlof # للاستخدام كأداة سطر أوامر في أي مكان
|
|
23
|
+
# أو داخل مشروع:
|
|
24
|
+
npm install dlof
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## سطر الأوامر
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
dlof view <file.dlof|file.dlofpkg> [-o out.html] # يبني عارض HTML
|
|
31
|
+
dlof play <file.dlofSeries|folder> [-o out.html] # يبني مشغّل HTML تفاعلي
|
|
32
|
+
dlof validate <file.dlof> # تحقق من الصحة
|
|
33
|
+
dlof pack <package.dlof> [-o out.dlofpkg] # تحزيم ملف dlof واحد
|
|
34
|
+
dlof pack-series <folder> [-o out.dlofSeries] # تحزيم مجلد سلسلة
|
|
35
|
+
dlof unpack <file.dlofpkg|file.dlofSeries> <outDir> # فك الضغط
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
أمثلة:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
dlof view ep01.dlofpkg -o ep01.html
|
|
42
|
+
dlof play MySeries.dlofSeries -o player.html
|
|
43
|
+
dlof play ./MySeries -o player.html # مجلد سلسلة غير مضغوط أيضاً مدعوم
|
|
44
|
+
dlof validate ch01.dlof
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## الاستخدام البرمجي
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import {
|
|
51
|
+
parseDlof, serializeDlof, validateDlof,
|
|
52
|
+
readDlofPkg, writeDlofPkg,
|
|
53
|
+
readDlofSeries, writeDlofSeries,
|
|
54
|
+
renderViewerHtml, renderPlayerHtml,
|
|
55
|
+
resolveLoopChain,
|
|
56
|
+
} from "dlof";
|
|
57
|
+
import * as fs from "fs";
|
|
58
|
+
|
|
59
|
+
// تحليل ملف .dlof
|
|
60
|
+
const doc = parseDlof(fs.readFileSync("ch01.dlof", "utf8"));
|
|
61
|
+
console.log(doc.metadata.title, doc.loopLinks.next?.ref);
|
|
62
|
+
|
|
63
|
+
// التحقق
|
|
64
|
+
const result = validateDlof(doc);
|
|
65
|
+
if (!result.valid) console.error(result.issues);
|
|
66
|
+
|
|
67
|
+
// قراءة حزمة .dlofpkg وبناء عارض HTML منها
|
|
68
|
+
const pkg = readDlofPkg(fs.readFileSync("ep01.dlofpkg"));
|
|
69
|
+
fs.writeFileSync("ep01.view.html", renderViewerHtml(pkg));
|
|
70
|
+
|
|
71
|
+
// قراءة سلسلة كاملة وبناء مشغّل تفاعلي
|
|
72
|
+
const series = readDlofSeries(fs.readFileSync("MySeries.dlofSeries"));
|
|
73
|
+
fs.writeFileSync("player.html", renderPlayerHtml(series));
|
|
74
|
+
|
|
75
|
+
// حلّ سلسلة التنقّل يدوياً (previous/next) بلا بناء عارض
|
|
76
|
+
const chain = resolveLoopChain(series.documents, series.rootFileName);
|
|
77
|
+
chain.forEach((e) => console.log(e.fileName, "->", e.document.metadata.title));
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## البنية المعمارية
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
src/
|
|
84
|
+
types.ts الأنواع المطابقة لـ docs/SPECIFICATION.md
|
|
85
|
+
xml/ محلّل ومُصدِّر XML خاص (بلا اعتماديات)
|
|
86
|
+
dlof/ تحليل، تصدير، والتحقق من ملفات .dlof
|
|
87
|
+
zip/ قارئ وكاتب ZIP خاص (يستخدم zlib المدمجة فقط)
|
|
88
|
+
packages/ قراءة/كتابة .dlofpkg و .dlofSeries
|
|
89
|
+
loop/ حلّ سلسلة الحلقة (previous/next) عبر loopLinks
|
|
90
|
+
viewer/ عارض HTML لمستند/حزمة واحدة
|
|
91
|
+
player/ مشغّل HTML تفاعلي لسلسلة/حلقة كاملة
|
|
92
|
+
cli.ts أداة سطر الأوامر
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## ملاحظات
|
|
96
|
+
|
|
97
|
+
- الصفحات الناتجة (عارض/مشغّل) ملفات HTML **مستقلة تماماً** — تُفتح مباشرة في أي متصفح، ولا تحتاج خادماً.
|
|
98
|
+
- المشغّل يضمّن ملفات الوسائط (فيديو/صوت) كـ `data:` URL إذا كان حجمها أقل من 20 ميجابايت افتراضياً (قابل للتغيير عبر `PlayerOptions.maxInlineMediaBytes`)، وإلا يعرض إشارة مرجعية فقط.
|
|
99
|
+
- يتماشى تنفيذ ZIP مع مواصفة PKZIP القياسية (رؤوس ملفات محلية + فهرس مركزي + EOCD)، ويدعم أسلوبي STORE و DEFLATE، وهو ما تتطلبه كل من `.dlofpkg` و `.dlofSeries`.
|
|
100
|
+
|
|
101
|
+
## الترخيص
|
|
102
|
+
|
|
103
|
+
MIT
|
package/bin/dlof.js
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const parseDlof_1 = require("./dlof/parseDlof");
|
|
40
|
+
const validate_1 = require("./dlof/validate");
|
|
41
|
+
const dlofpkg_1 = require("./packages/dlofpkg");
|
|
42
|
+
const dlofSeries_1 = require("./packages/dlofSeries");
|
|
43
|
+
const renderViewerHtml_1 = require("./viewer/renderViewerHtml");
|
|
44
|
+
const renderPlayerHtml_1 = require("./player/renderPlayerHtml");
|
|
45
|
+
const HELP = `dlof — أداة سطر أوامر لصيغة DLoF (Document Loop Format)
|
|
46
|
+
|
|
47
|
+
الاستخدام:
|
|
48
|
+
dlof view <file.dlof|file.dlofpkg> [-o out.html] يبني عارض HTML لملف أو حزمة
|
|
49
|
+
dlof play <file.dlofSeries|folder> [-o out.html] يبني مشغّل HTML تفاعلي لسلسلة/حلقة كاملة
|
|
50
|
+
dlof validate <file.dlof> يتحقق من صحة ملف مقابل المواصفة
|
|
51
|
+
dlof pack <package.dlof> [-o out.dlofpkg] يحزم ملف dlof واحد كـ .dlofpkg
|
|
52
|
+
dlof pack-series <folder> [-o out.dlofSeries] يحزم مجلد سلسلة كـ .dlofSeries
|
|
53
|
+
dlof unpack <file.dlofpkg|file.dlofSeries> <outDir> يفك ضغط حزمة إلى مجلد
|
|
54
|
+
|
|
55
|
+
أمثلة:
|
|
56
|
+
dlof view ep01.dlofpkg -o ep01.html
|
|
57
|
+
dlof play MySeries.dlofSeries -o player.html
|
|
58
|
+
dlof play ./MySeries -o player.html (مجلد سلسلة غير مضغوط)
|
|
59
|
+
dlof validate ch01.dlof
|
|
60
|
+
`;
|
|
61
|
+
function readArgFlag(args, flag) {
|
|
62
|
+
const idx = args.indexOf(flag);
|
|
63
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
64
|
+
}
|
|
65
|
+
function loadDocumentLoop(filePath) {
|
|
66
|
+
const xml = fs.readFileSync(filePath, "utf8");
|
|
67
|
+
return (0, parseDlof_1.parseDlof)(xml, path.basename(filePath));
|
|
68
|
+
}
|
|
69
|
+
function loadSeriesFromFolder(folder) {
|
|
70
|
+
const documents = {};
|
|
71
|
+
const assets = {};
|
|
72
|
+
function walk(dir, relBase) {
|
|
73
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
74
|
+
const full = path.join(dir, entry.name);
|
|
75
|
+
const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
76
|
+
if (entry.isDirectory()) {
|
|
77
|
+
walk(full, rel);
|
|
78
|
+
}
|
|
79
|
+
else if ((0, parseDlof_1.isDlofFileName)(entry.name) && !relBase) {
|
|
80
|
+
documents[entry.name] = (0, parseDlof_1.parseDlof)(fs.readFileSync(full, "utf8"), entry.name);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
assets[rel] = fs.readFileSync(full);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
walk(folder, "");
|
|
88
|
+
let rootFileName = Object.keys(documents).find((f) => documents[f].loopLinks.loopRoot);
|
|
89
|
+
if (!rootFileName && documents["series-index.dlof"])
|
|
90
|
+
rootFileName = "series-index.dlof";
|
|
91
|
+
return { name: path.basename(folder), documents, rootFileName, assets };
|
|
92
|
+
}
|
|
93
|
+
function writeOutput(defaultName, outFlag, content) {
|
|
94
|
+
const outPath = outFlag ?? defaultName;
|
|
95
|
+
fs.writeFileSync(outPath, content);
|
|
96
|
+
console.log(`✅ تم الإنشاء: ${path.resolve(outPath)}`);
|
|
97
|
+
}
|
|
98
|
+
function cmdView(args) {
|
|
99
|
+
const input = args[0];
|
|
100
|
+
if (!input)
|
|
101
|
+
return fail("يرجى تحديد ملف .dlof أو .dlofpkg");
|
|
102
|
+
const out = readArgFlag(args, "-o") ?? readArgFlag(args, "--out");
|
|
103
|
+
const lower = input.toLowerCase();
|
|
104
|
+
let html;
|
|
105
|
+
if (lower.endsWith(".dlofpkg")) {
|
|
106
|
+
const pkg = (0, dlofpkg_1.readDlofPkg)(fs.readFileSync(input));
|
|
107
|
+
html = (0, renderViewerHtml_1.renderViewerHtml)(pkg);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
html = (0, renderViewerHtml_1.renderViewerHtml)(loadDocumentLoop(input));
|
|
111
|
+
}
|
|
112
|
+
writeOutput(defaultOutName(input, ".view.html"), out, html);
|
|
113
|
+
}
|
|
114
|
+
function cmdPlay(args) {
|
|
115
|
+
const input = args[0];
|
|
116
|
+
if (!input)
|
|
117
|
+
return fail("يرجى تحديد ملف .dlofSeries أو مجلد سلسلة");
|
|
118
|
+
const out = readArgFlag(args, "-o") ?? readArgFlag(args, "--out");
|
|
119
|
+
const stat = fs.statSync(input);
|
|
120
|
+
let series;
|
|
121
|
+
if (stat.isDirectory()) {
|
|
122
|
+
series = loadSeriesFromFolder(input);
|
|
123
|
+
}
|
|
124
|
+
else if (input.toLowerCase().endsWith(".dlofseries")) {
|
|
125
|
+
series = (0, dlofSeries_1.readDlofSeries)(fs.readFileSync(input));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// ملف .dlof منفرد: عامله كحلقة من عنصر واحد
|
|
129
|
+
const doc = loadDocumentLoop(input);
|
|
130
|
+
series = { name: path.basename(input, path.extname(input)), documents: { [path.basename(input)]: doc }, rootFileName: path.basename(input), assets: {} };
|
|
131
|
+
}
|
|
132
|
+
const html = (0, renderPlayerHtml_1.renderPlayerHtml)(series);
|
|
133
|
+
writeOutput(defaultOutName(input, ".player.html"), out, html);
|
|
134
|
+
}
|
|
135
|
+
function cmdValidate(args) {
|
|
136
|
+
const input = args[0];
|
|
137
|
+
if (!input)
|
|
138
|
+
return fail("يرجى تحديد ملف .dlof");
|
|
139
|
+
const doc = loadDocumentLoop(input);
|
|
140
|
+
const result = (0, validate_1.validateDlof)(doc);
|
|
141
|
+
for (const issue of result.issues) {
|
|
142
|
+
const prefix = issue.level === "error" ? "❌ خطأ" : "⚠️ تنبيه";
|
|
143
|
+
console.log(`${prefix}: ${issue.message}`);
|
|
144
|
+
}
|
|
145
|
+
console.log(result.valid ? "\n✅ الملف صالح." : "\n❌ الملف يحتوي أخطاء.");
|
|
146
|
+
if (!result.valid)
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
}
|
|
149
|
+
function cmdPack(args) {
|
|
150
|
+
const input = args[0];
|
|
151
|
+
if (!input)
|
|
152
|
+
return fail("يرجى تحديد ملف package.dlof لتحزيمه");
|
|
153
|
+
const out = readArgFlag(args, "-o") ?? readArgFlag(args, "--out");
|
|
154
|
+
const doc = loadDocumentLoop(input);
|
|
155
|
+
const buf = (0, dlofpkg_1.writeDlofPkg)({ meta: (0, dlofpkg_1.buildDefaultMeta)(doc), document: doc, attachments: {} });
|
|
156
|
+
writeOutput(defaultOutName(input, ".dlofpkg"), out, buf);
|
|
157
|
+
}
|
|
158
|
+
function cmdPackSeries(args) {
|
|
159
|
+
const input = args[0];
|
|
160
|
+
if (!input)
|
|
161
|
+
return fail("يرجى تحديد مجلد السلسلة لتحزيمه");
|
|
162
|
+
const out = readArgFlag(args, "-o") ?? readArgFlag(args, "--out");
|
|
163
|
+
const series = loadSeriesFromFolder(input);
|
|
164
|
+
const buf = (0, dlofSeries_1.writeDlofSeries)(series);
|
|
165
|
+
writeOutput(defaultOutName(input, ".dlofSeries"), out, buf);
|
|
166
|
+
}
|
|
167
|
+
function cmdUnpack(args) {
|
|
168
|
+
const input = args[0];
|
|
169
|
+
const outDir = args[1];
|
|
170
|
+
if (!input || !outDir)
|
|
171
|
+
return fail("الاستخدام: dlof unpack <file> <outDir>");
|
|
172
|
+
const lower = input.toLowerCase();
|
|
173
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
174
|
+
if (lower.endsWith(".dlofpkg")) {
|
|
175
|
+
const pkg = (0, dlofpkg_1.readDlofPkg)(fs.readFileSync(input));
|
|
176
|
+
fs.writeFileSync(path.join(outDir, "package.dlof"), Buffer.from(require("./dlof/serializeDlof").serializeDlof(pkg.document)));
|
|
177
|
+
fs.writeFileSync(path.join(outDir, "meta.json"), JSON.stringify(pkg.meta, null, 2));
|
|
178
|
+
for (const [rel, data] of Object.entries(pkg.attachments)) {
|
|
179
|
+
const full = path.join(outDir, rel);
|
|
180
|
+
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
181
|
+
fs.writeFileSync(full, data);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (lower.endsWith(".dlofseries")) {
|
|
185
|
+
const series = (0, dlofSeries_1.readDlofSeries)(fs.readFileSync(input));
|
|
186
|
+
const { serializeDlof } = require("./dlof/serializeDlof");
|
|
187
|
+
for (const [fileName, doc] of Object.entries(series.documents)) {
|
|
188
|
+
fs.writeFileSync(path.join(outDir, fileName), serializeDlof(doc));
|
|
189
|
+
}
|
|
190
|
+
for (const [rel, data] of Object.entries(series.assets)) {
|
|
191
|
+
const full = path.join(outDir, rel);
|
|
192
|
+
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
193
|
+
fs.writeFileSync(full, data);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
return fail("امتداد غير مدعوم لفك الضغط (المتوقع .dlofpkg أو .dlofSeries)");
|
|
198
|
+
}
|
|
199
|
+
console.log(`✅ تم فك الضغط إلى: ${path.resolve(outDir)}`);
|
|
200
|
+
}
|
|
201
|
+
function defaultOutName(input, suffix) {
|
|
202
|
+
const base = path.basename(input, path.extname(input));
|
|
203
|
+
return path.join(path.dirname(input), base + suffix);
|
|
204
|
+
}
|
|
205
|
+
function fail(message) {
|
|
206
|
+
console.error(`❌ ${message}\n`);
|
|
207
|
+
console.log(HELP);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
function main() {
|
|
211
|
+
const [, , command, ...rest] = process.argv;
|
|
212
|
+
switch (command) {
|
|
213
|
+
case "view":
|
|
214
|
+
return cmdView(rest);
|
|
215
|
+
case "play":
|
|
216
|
+
return cmdPlay(rest);
|
|
217
|
+
case "validate":
|
|
218
|
+
return cmdValidate(rest);
|
|
219
|
+
case "pack":
|
|
220
|
+
return cmdPack(rest);
|
|
221
|
+
case "pack-series":
|
|
222
|
+
return cmdPackSeries(rest);
|
|
223
|
+
case "unpack":
|
|
224
|
+
return cmdUnpack(rest);
|
|
225
|
+
case undefined:
|
|
226
|
+
case "-h":
|
|
227
|
+
case "--help":
|
|
228
|
+
case "help":
|
|
229
|
+
console.log(HELP);
|
|
230
|
+
return;
|
|
231
|
+
default:
|
|
232
|
+
console.error(`أمر غير معروف: ${command}\n`);
|
|
233
|
+
console.log(HELP);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
main();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DocumentLoop } from "../types";
|
|
2
|
+
/** يحلّل نص XML كامل لملف .dlof (أو .ep / .episode) إلى كائن DocumentLoop مُنمَّط */
|
|
3
|
+
export declare function parseDlof(xmlSource: string, sourceFileName?: string): DocumentLoop;
|
|
4
|
+
/** امتدادات ملفات dlof المقبولة (المواصفة، القسم 11) */
|
|
5
|
+
export declare const DLOF_EXTENSIONS: string[];
|
|
6
|
+
export declare function isDlofFileName(name: string): boolean;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DLOF_EXTENSIONS = void 0;
|
|
4
|
+
exports.parseDlof = parseDlof;
|
|
5
|
+
exports.isDlofFileName = isDlofFileName;
|
|
6
|
+
const parser_1 = require("../xml/parser");
|
|
7
|
+
function parseMetadata(root) {
|
|
8
|
+
const meta = (0, parser_1.child)(root, "metadata");
|
|
9
|
+
const tagsNode = (0, parser_1.child)(meta, "tags");
|
|
10
|
+
const tags = tagsNode ? (0, parser_1.children)(tagsNode, "tag").map((t) => t.text) : undefined;
|
|
11
|
+
return {
|
|
12
|
+
title: (0, parser_1.childText)(meta, "title") ?? "",
|
|
13
|
+
domain: (0, parser_1.childText)(meta, "domain") ?? "custom",
|
|
14
|
+
author: (0, parser_1.childText)(meta, "author"),
|
|
15
|
+
createdAt: (0, parser_1.childText)(meta, "createdAt"),
|
|
16
|
+
updatedAt: (0, parser_1.childText)(meta, "updatedAt"),
|
|
17
|
+
language: (0, parser_1.childText)(meta, "language") ?? "ar",
|
|
18
|
+
tags,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function parseLoopLinks(root) {
|
|
22
|
+
const ll = (0, parser_1.child)(root, "loopLinks");
|
|
23
|
+
const prevNode = (0, parser_1.child)(ll, "previous");
|
|
24
|
+
const nextNode = (0, parser_1.child)(ll, "next");
|
|
25
|
+
const previous = prevNode
|
|
26
|
+
? { ref: prevNode.attrs.ref ?? "", title: prevNode.attrs.title }
|
|
27
|
+
: undefined;
|
|
28
|
+
const next = nextNode
|
|
29
|
+
? { ref: nextNode.attrs.ref ?? "", title: nextNode.attrs.title }
|
|
30
|
+
: undefined;
|
|
31
|
+
const loopRoot = ((0, parser_1.childText)(ll, "loopRoot") ?? "false").trim().toLowerCase() === "true";
|
|
32
|
+
return { previous, next, loopRoot };
|
|
33
|
+
}
|
|
34
|
+
function numOrUndef(v) {
|
|
35
|
+
if (v === undefined || v === "")
|
|
36
|
+
return undefined;
|
|
37
|
+
const n = Number(v);
|
|
38
|
+
return Number.isFinite(n) ? n : undefined;
|
|
39
|
+
}
|
|
40
|
+
function parseContent(root) {
|
|
41
|
+
const c = (0, parser_1.child)(root, "content");
|
|
42
|
+
if (!c) {
|
|
43
|
+
return { kind: "genericItem" };
|
|
44
|
+
}
|
|
45
|
+
const generic = (0, parser_1.child)(c, "genericItem");
|
|
46
|
+
if (generic) {
|
|
47
|
+
return {
|
|
48
|
+
kind: "genericItem",
|
|
49
|
+
customType: generic.attrs.customType,
|
|
50
|
+
type: (0, parser_1.childText)(generic, "type"),
|
|
51
|
+
element: (0, parser_1.childText)(generic, "element"),
|
|
52
|
+
body: (0, parser_1.childText)(generic, "body"),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const qa = (0, parser_1.child)(c, "qaItem");
|
|
56
|
+
if (qa) {
|
|
57
|
+
return {
|
|
58
|
+
kind: "qaItem",
|
|
59
|
+
question: (0, parser_1.childText)(qa, "question") ?? "",
|
|
60
|
+
answer: (0, parser_1.childText)(qa, "answer") ?? "",
|
|
61
|
+
explanation: (0, parser_1.childText)(qa, "explanation"),
|
|
62
|
+
difficulty: (0, parser_1.childText)(qa, "difficulty"),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const chapter = (0, parser_1.child)(c, "bookChapter");
|
|
66
|
+
if (chapter) {
|
|
67
|
+
return {
|
|
68
|
+
kind: "bookChapter",
|
|
69
|
+
chapterTitle: (0, parser_1.childText)(chapter, "chapterTitle") ?? "",
|
|
70
|
+
text: (0, parser_1.childText)(chapter, "text") ?? "",
|
|
71
|
+
chapterNumber: numOrUndef((0, parser_1.childText)(chapter, "chapterNumber")),
|
|
72
|
+
summary: (0, parser_1.childText)(chapter, "summary"),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const term = (0, parser_1.child)(c, "termDefinition");
|
|
76
|
+
if (term) {
|
|
77
|
+
return {
|
|
78
|
+
kind: "termDefinition",
|
|
79
|
+
term: (0, parser_1.childText)(term, "term") ?? "",
|
|
80
|
+
definition: (0, parser_1.childText)(term, "definition") ?? "",
|
|
81
|
+
example: (0, parser_1.childText)(term, "example"),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const info = (0, parser_1.child)(c, "infoExplain");
|
|
85
|
+
if (info) {
|
|
86
|
+
return {
|
|
87
|
+
kind: "infoExplain",
|
|
88
|
+
topic: (0, parser_1.childText)(info, "topic") ?? "",
|
|
89
|
+
explanation: (0, parser_1.childText)(info, "explanation") ?? "",
|
|
90
|
+
source: (0, parser_1.childText)(info, "source"),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const episode = (0, parser_1.child)(c, "episodeItem");
|
|
94
|
+
if (episode) {
|
|
95
|
+
return {
|
|
96
|
+
kind: "episodeItem",
|
|
97
|
+
episodeNumber: numOrUndef((0, parser_1.childText)(episode, "episodeNumber")),
|
|
98
|
+
seasonNumber: numOrUndef((0, parser_1.childText)(episode, "seasonNumber")),
|
|
99
|
+
episodeTitle: (0, parser_1.childText)(episode, "episodeTitle") ?? "",
|
|
100
|
+
synopsis: (0, parser_1.childText)(episode, "synopsis"),
|
|
101
|
+
duration: numOrUndef((0, parser_1.childText)(episode, "duration")),
|
|
102
|
+
seriesTitle: (0, parser_1.childText)(episode, "seriesTitle"),
|
|
103
|
+
mediaRef: (0, parser_1.childText)(episode, "mediaRef"),
|
|
104
|
+
releaseDate: (0, parser_1.childText)(episode, "releaseDate"),
|
|
105
|
+
body: (0, parser_1.childText)(episode, "body"),
|
|
106
|
+
thumbnailBase64: (0, parser_1.childText)(episode, "thumbnailBase64"),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// نوع محتوى غير معروف: يُرجع كعنصر عام لتجنّب الانهيار (توصية القسم 10 من المواصفة)
|
|
110
|
+
return { kind: "genericItem", body: c.text };
|
|
111
|
+
}
|
|
112
|
+
function parseAttachments(root) {
|
|
113
|
+
const wrap = (0, parser_1.child)(root, "attachments");
|
|
114
|
+
if (!wrap)
|
|
115
|
+
return undefined;
|
|
116
|
+
const list = (0, parser_1.children)(wrap, "attachment");
|
|
117
|
+
if (!list.length)
|
|
118
|
+
return undefined;
|
|
119
|
+
return list.map((a) => ({
|
|
120
|
+
id: a.attrs.id ?? "",
|
|
121
|
+
fileName: a.attrs.fileName ?? "",
|
|
122
|
+
mimeType: a.attrs.mimeType ?? "application/octet-stream",
|
|
123
|
+
kind: a.attrs.kind ?? "file",
|
|
124
|
+
sizeBytes: numOrUndef(a.attrs.sizeBytes),
|
|
125
|
+
data: (0, parser_1.childText)(a, "data"),
|
|
126
|
+
uri: (0, parser_1.childText)(a, "uri"),
|
|
127
|
+
caption: (0, parser_1.childText)(a, "caption"),
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
function parseTemplate(root) {
|
|
131
|
+
const t = (0, parser_1.child)(root, "template");
|
|
132
|
+
if (!t)
|
|
133
|
+
return undefined;
|
|
134
|
+
return {
|
|
135
|
+
ref: t.attrs.ref,
|
|
136
|
+
primaryColor: t.attrs.primaryColor,
|
|
137
|
+
secondaryColor: t.attrs.secondaryColor,
|
|
138
|
+
backgroundColor: t.attrs.backgroundColor,
|
|
139
|
+
textColor: t.attrs.textColor,
|
|
140
|
+
fontFamily: t.attrs.fontFamily,
|
|
141
|
+
layout: t.attrs.layout,
|
|
142
|
+
headerAttachmentRef: t.attrs.headerAttachmentRef,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function parseMediaFolder(root) {
|
|
146
|
+
const mf = (0, parser_1.child)(root, "mediaFolder");
|
|
147
|
+
if (!mf)
|
|
148
|
+
return undefined;
|
|
149
|
+
const files = (0, parser_1.children)(mf, "mediaFile");
|
|
150
|
+
if (!files.length)
|
|
151
|
+
return undefined;
|
|
152
|
+
return files.map((f) => ({
|
|
153
|
+
path: f.attrs.path ?? "",
|
|
154
|
+
kind: f.attrs.kind ?? "file",
|
|
155
|
+
label: f.attrs.label,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
/** يحلّل نص XML كامل لملف .dlof (أو .ep / .episode) إلى كائن DocumentLoop مُنمَّط */
|
|
159
|
+
function parseDlof(xmlSource, sourceFileName) {
|
|
160
|
+
const root = (0, parser_1.parseXml)(xmlSource);
|
|
161
|
+
if (root.tag !== "documentLoop") {
|
|
162
|
+
throw new Error(`ملف dlof غير صالح: العنصر الجذر يجب أن يكون <documentLoop> وليس <${root.tag}>`);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
version: root.attrs.version ?? "1.0",
|
|
166
|
+
id: root.attrs.id ?? "",
|
|
167
|
+
metadata: parseMetadata(root),
|
|
168
|
+
loopLinks: parseLoopLinks(root),
|
|
169
|
+
content: parseContent(root),
|
|
170
|
+
attachments: parseAttachments(root),
|
|
171
|
+
template: parseTemplate(root),
|
|
172
|
+
mediaFolder: parseMediaFolder(root),
|
|
173
|
+
sourceFileName,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** امتدادات ملفات dlof المقبولة (المواصفة، القسم 11) */
|
|
177
|
+
exports.DLOF_EXTENSIONS = [".dlof", ".ep", ".episode"];
|
|
178
|
+
function isDlofFileName(name) {
|
|
179
|
+
const lower = name.toLowerCase();
|
|
180
|
+
return exports.DLOF_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
181
|
+
}
|