1 /* Written by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module booktext
/*is aliced*/;
35 // ////////////////////////////////////////////////////////////////////////// //
41 string diskfile
; // not set by `loadBookInfo()`!
45 BookInfo
loadBookInfo (const(char)[] fname
) {
46 static BookInfo
loadFile (VFile fl
) {
48 auto sax
= new SaxyEx();
49 bool complete
= false;
50 string authorFirst
, authorLast
;
52 inout(char)[] strip (inout(char)[] s
) inout {
53 while (s
.length
&& s
.ptr
[0] <= ' ') s
= s
[1..$];
54 while (s
.length
&& s
[$-1] <= ' ') s
= s
[0..$-1];
60 sax
.onOpen("/FictionBook/description/title-info/sequence", (char[] text
, char[][string
] attrs
) {
61 if (auto sn
= "name" in attrs
) {
62 res
.seqname
= strip(*sn
).dup
;
63 if (auto id
= "number" in attrs
) {
65 res
.seqnum
= to
!uint(*id
);
66 if (res
.seqnum
< 1 || res
.seqnum
> 8192) res
.seqname
= null;
71 if (res
.seqname
.length
== 0) { res
.seqname
= null; res
.seqnum
= 0; }
74 sax
.onClose("/FictionBook/description", (char[] text
) {
76 throw new Exception("done"); // sorry
79 // author's first name
80 sax
.onContent("/FictionBook/description/title-info/author/first-name", (char[] text
) {
81 authorFirst
= strip(text
).idup
;
84 sax
.onContent("/FictionBook/description/title-info/author/last-name", (char[] text
) {
85 authorLast
= strip(text
).idup
;
88 sax
.onContent("/FictionBook/description/title-info/book-title", (char[] text
) {
89 res
.title
= strip(text
).idup
;
93 } catch (Exception e
) {
94 if (!complete
) throw e
;
96 if (authorFirst
.length
== 0) {
97 authorFirst
= authorLast
;
99 } else if (authorLast
.length
!= 0) {
100 authorFirst
~= " "~authorLast
;
102 if (authorFirst
.length
== 0 && res
.title
.length
== 0) throw new Exception("no book title found");
103 res
.author
= authorFirst
;
107 import std
.path
: extension
;
108 if (strEquCI(fname
.extension
, ".zip")) {
110 scope(exit
) vfsRemovePack(fname
);
111 foreach (immutable idx
, ref de; vfsFileList
) {
112 if (strEquCI(de.name
.extension
, ".fb2")) return loadFile(vfsOpenFile(de.name
));
114 throw new Exception("no fb2 file found in '"~fname
.idup
~"'");
116 return loadFile(vfsOpenFile(fname
));
121 // ////////////////////////////////////////////////////////////////////////// //
123 string name
; // empty: text node
132 @property inout(Tag
) firstChild () inout pure nothrow @trusted @nogc { pragma(inline
, true); return (children
.length ? children
.ptr
[0] : null); }
133 @property inout(Tag
) lastChild () inout pure nothrow @trusted @nogc { pragma(inline
, true); return (children
.length ? children
[$-1] : null); }
135 string
textContent () const {
136 if (name
.length
== 0) return text
;
138 foreach (const Tag t
; children
) res
~= t
.textContent
;
142 string
toStringNice (int indent
=0) const {
144 void addIndent () { foreach (immutable _
; 0..indent
) res
~= ' '; }
145 void newLine () { res
~= '\n'; foreach (immutable _
; 0..indent
) res
~= ' '; }
146 if (name
.length
== 0) return text
;
148 if (children
.length
== 0) res
~= '/';
150 if (id
.length
) { res
~= " id="; res
~= id
; }
151 if (href
.length
) { res
~= " href="; res
~= href
; }
153 if (children
.length
) {
154 if (indent
>= 0) indent
+= 2;
155 foreach (const Tag cc
; children
) {
156 if (indent
>= 0) newLine();
157 res
~= cc
.toStringNice(indent
);
159 if (indent
>= 0) indent
-= 2;
160 if (indent
>= 0) newLine();
168 override string
toString () const { return toStringNice(int.min
); }
172 // ////////////////////////////////////////////////////////////////////////// //
173 final class BookText
{
182 this (const(char)[] fname
) { loadFile(fname
); }
183 this (VFile fl
) { loadFile(fl
); }
186 void loadFile (VFile fl
) {
187 auto sax
= new SaxyEx();
189 string
norm (const(char)[] s
) {
191 foreach (char ch
; s
) {
192 if (ch
<= ' ' || ch
== 127) {
193 if (res
.length
&& res
[$-1] > ' ') res
~= ch
;
202 sax
.onOpen("/FictionBook/description/title-info/sequence", (char[] text
, char[][string
] attrs
) {
203 if (auto sn
= "name" in attrs
) {
204 sequence
= (*sn
).dup
;
205 if (auto id
= "number" in attrs
) {
206 import std
.conv
: to
;
207 seqnum
= to
!uint(*id
);
208 if (seqnum
< 1 || seqnum
> 8192) sequence
= null;
213 if (sequence
.length
== 0) { sequence
= null; seqnum
= 0; }
216 // author's first name
217 sax
.onContent("/FictionBook/description/title-info/author/first-name", (char[] text
) {
218 authorFirst
= norm(text
);
220 // author's last name
221 sax
.onContent("/FictionBook/description/title-info/author/last-name", (char[] text
) {
222 authorLast
= norm(text
);
225 sax
.onContent("/FictionBook/description/title-info/book-title", (char[] text
) {
231 sax
.onOpen("/FictionBook/body", (char[] text
) {
232 if (content
is null) {
234 content
.name
= "body";
239 sax
.onClose("/FictionBook/body", (char[] text
) {
240 if (content
is null) assert(0, "wtf?!");
241 tagStack
.length
-= 1;
242 tagStack
.assumeSafeAppend
;
245 sax
.onOpen("/FictionBook/body/+", (char[] text
, char[][string
] attrs
) {
246 auto tag
= new Tag();
247 tag
.name
= text
.idup
;
248 tag
.parent
= tagStack
[$-1];
250 if (auto ls = tag.parent.lastChild) {
251 ls.nextSibling = tag;
252 tag.prevSibling = ls;
255 tag
.parent
.children
~= tag
;
256 if (auto p
= "id" in attrs
) tag
.id
= norm(*p
);
257 if (auto p
= "href" in attrs
) tag
.href
= norm(*p
);
261 sax
.onClose("/FictionBook/body/+", (char[] text
) {
262 tagStack
.length
-= 1;
263 tagStack
.assumeSafeAppend
;
266 sax
.onContent("/FictionBook/body/+", (char[] text
) {
267 auto tag
= new Tag();
269 tag
.parent
= tagStack
[$-1];
271 if (auto ls = tag.parent.lastChild) {
272 ls.nextSibling = tag;
273 tag.prevSibling = ls;
276 tag
.parent
.children
~= tag
;
277 tag
.text
~= text
.idup
;
282 if (content
is null) {
284 content
.name
= "body";
288 void loadFile (const(char)[] fname
) {
289 import std
.path
: extension
;
290 if (strEquCI(fname
.extension
, ".zip")) {
292 scope(exit
) vfsRemovePack(fname
);
293 foreach (immutable idx
, ref de; vfsFileList
) {
294 if (strEquCI(de.name
.extension
, ".fb2")) { loadFile(vfsOpenFile(de.name
)); return; }
296 throw new Exception("no fb2 file found in '"~fname
.idup
~"'");
298 loadFile(vfsOpenFile(fname
));