Coverage Report

Created: 2026-09-14 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libe-book/src/lib/FictionBook2Parser.cpp
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/*
3
 * This file is part of the libe-book project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include <cassert>
11
12
#include "libebook_utils.h"
13
#include "libebook_xml.h"
14
15
#include "FictionBook2BinaryContext.h"
16
#include "FictionBook2BlockContext.h"
17
#include "FictionBook2Collector.h"
18
#include "FictionBook2ContentCollector.h"
19
#include "FictionBook2ExtrasCollector.h"
20
#include "FictionBook2Parser.h"
21
#include "FictionBook2MetadataCollector.h"
22
#include "FictionBook2MetadataContext.h"
23
#include "FictionBook2TableContext.h"
24
#include "FictionBook2TextContext.h"
25
#include "FictionBook2Token.h"
26
27
namespace libebook
28
{
29
30
namespace
31
{
32
33
class DocumentContext : public FictionBook2ParserContext
34
{
35
  // no copying
36
  DocumentContext(const DocumentContext &other);
37
  DocumentContext &operator=(const DocumentContext &other);
38
39
public:
40
  DocumentContext(FictionBook2Collector::NoteMap_t &notes, FictionBook2Collector::BinaryMap_t &bitmaps, librevenge::RVNGTextInterface *document = nullptr);
41
42
private:
43
  std::shared_ptr<FictionBook2XMLParserContext> leaveContext() const override;
44
45
  std::shared_ptr<FictionBook2XMLParserContext> element(const FictionBook2TokenData &name, const FictionBook2TokenData &ns) override;
46
  void startOfElement() override;
47
  void endOfElement() override;
48
  void attribute(const FictionBook2TokenData &name, const FictionBook2TokenData *ns, const char *value) override;
49
  void endOfAttributes() override;
50
  void text(const char *text) override;
51
52
private:
53
  librevenge::RVNGTextInterface *const m_document;
54
  FictionBook2Collector::NoteMap_t &m_notes;
55
  FictionBook2Collector::BinaryMap_t &m_bitmaps;
56
  bool m_generating;
57
};
58
59
class FictionBookGeneratorContext : public FictionBook2NodeContextBase
60
{
61
  // no copying
62
  FictionBookGeneratorContext(const FictionBookGeneratorContext &other);
63
  FictionBookGeneratorContext &operator=(const FictionBookGeneratorContext &other);
64
65
public:
66
  FictionBookGeneratorContext(std::shared_ptr<FictionBook2ParserContext> parentContext, const FictionBook2Collector::NoteMap_t &notes, const FictionBook2Collector::BinaryMap_t &bitmaps, librevenge::RVNGTextInterface *document);
67
68
private:
69
  std::shared_ptr<FictionBook2XMLParserContext> element(const FictionBook2TokenData &name, const FictionBook2TokenData &ns) override;
70
  void startOfElement() override;
71
  void endOfElement() override;
72
  void attribute(const FictionBook2TokenData &name, const FictionBook2TokenData *ns, const char *value) override;
73
74
private:
75
  librevenge::RVNGTextInterface *const m_document;
76
  librevenge::RVNGPropertyList m_metadata;
77
  FictionBook2MetadataCollector m_metadataCollector;
78
  FictionBook2ContentCollector m_contentCollector;
79
  bool m_bodyRead;
80
};
81
82
class FictionBookGathererContext : public FictionBook2NodeContextBase
83
{
84
  // no copying
85
  FictionBookGathererContext(const FictionBookGathererContext &other);
86
  FictionBookGathererContext &operator=(const FictionBookGathererContext &other);
87
88
public:
89
  FictionBookGathererContext(std::shared_ptr<FictionBook2ParserContext> parentContext, FictionBook2Collector::NoteMap_t &notes, FictionBook2Collector::BinaryMap_t &bitmaps);
90
91
private:
92
  std::shared_ptr<FictionBook2XMLParserContext> element(const FictionBook2TokenData &name, const FictionBook2TokenData &ns) override;
93
  void endOfElement() override;
94
  void attribute(const FictionBook2TokenData &name, const FictionBook2TokenData *ns, const char *value) override;
95
96
private:
97
  FictionBook2Collector::NoteMap_t &m_notes;
98
  FictionBook2Collector::BinaryMap_t &m_bitmaps;
99
  FictionBook2ExtrasCollector m_collector;
100
  bool m_firstBody;
101
};
102
103
class StylesheetContext : public FictionBook2NodeContextBase
104
{
105
};
106
107
}
108
109
namespace
110
{
111
112
FictionBookGeneratorContext::FictionBookGeneratorContext(
113
  std::shared_ptr<FictionBook2ParserContext> parentContext,
114
  const FictionBook2Collector::NoteMap_t &notes, const FictionBook2Collector::BinaryMap_t &bitmaps,
115
  librevenge::RVNGTextInterface *const document)
116
3.28k
  : FictionBook2NodeContextBase(parentContext)
117
3.28k
  , m_document(document)
118
3.28k
  , m_metadata()
119
3.28k
  , m_metadataCollector(m_metadata)
120
3.28k
  , m_contentCollector(m_document, m_metadata, notes, bitmaps)
121
3.28k
  , m_bodyRead(false)
122
3.28k
{
123
3.28k
}
124
125
std::shared_ptr<FictionBook2XMLParserContext> FictionBookGeneratorContext::element(const FictionBook2TokenData &name, const FictionBook2TokenData &ns)
126
11.2k
{
127
11.2k
  if (FictionBook2Token::NS_FICTIONBOOK == getFictionBook2TokenID(ns))
128
10.9k
  {
129
10.9k
    switch (getFictionBook2TokenID(name))
130
10.9k
    {
131
0
    case FictionBook2Token::stylesheet :
132
      // ignore
133
0
      break;
134
6.00k
    case FictionBook2Token::description :
135
6.00k
      return std::make_shared<FictionBook2DescriptionContext>(shared_from_this(), &m_metadataCollector);
136
2.35k
    case FictionBook2Token::body :
137
2.35k
    {
138
2.35k
      if (!m_bodyRead)
139
1.28k
      {
140
1.28k
        m_document->startDocument(librevenge::RVNGPropertyList());
141
1.28k
        m_document->setDocumentMetaData(m_metadata);
142
1.28k
        m_bodyRead = true;
143
1.28k
        return std::make_shared<FictionBook2BodyContext>(shared_from_this(), &m_contentCollector);
144
1.28k
      }
145
2.35k
    }
146
1.06k
    break;
147
2.63k
    default :
148
2.63k
      break;
149
10.9k
    }
150
10.9k
  }
151
152
4.00k
  return std::make_shared<FictionBook2SkipElementContext>(shared_from_this());
153
11.2k
}
154
155
void FictionBookGeneratorContext::startOfElement()
156
3.28k
{
157
3.28k
}
158
159
void FictionBookGeneratorContext::endOfElement()
160
2.96k
{
161
2.96k
  m_document->endDocument();
162
2.96k
}
163
164
void FictionBookGeneratorContext::attribute(const FictionBook2TokenData &, const FictionBook2TokenData *, const char *)
165
70
{
166
70
}
167
168
FictionBookGathererContext::FictionBookGathererContext(std::shared_ptr<FictionBook2ParserContext> parentContext, FictionBook2Collector::NoteMap_t &notes, FictionBook2Collector::BinaryMap_t &bitmaps)
169
5.70k
  : FictionBook2NodeContextBase(parentContext)
170
5.70k
  , m_notes(notes)
171
5.70k
  , m_bitmaps(bitmaps)
172
5.70k
  , m_collector(m_notes, m_bitmaps)
173
5.70k
  , m_firstBody(true)
174
5.70k
{
175
5.70k
}
176
177
std::shared_ptr<FictionBook2XMLParserContext> FictionBookGathererContext::element(const FictionBook2TokenData &name, const FictionBook2TokenData &ns)
178
22.1k
{
179
22.1k
  if (FictionBook2Token::NS_FICTIONBOOK == getFictionBook2TokenID(ns))
180
21.8k
  {
181
21.8k
    switch (getFictionBook2TokenID(name))
182
21.8k
    {
183
8.27k
    case FictionBook2Token::body :
184
8.27k
    {
185
8.27k
      if (m_firstBody)
186
3.19k
        m_firstBody = false;
187
5.07k
      else
188
5.07k
        return std::make_shared<FictionBook2BodyContext>(shared_from_this(), &m_collector);
189
8.27k
    }
190
3.19k
    break;
191
4.30k
    case FictionBook2Token::binary :
192
4.30k
      return std::make_shared<FictionBook2BinaryContext>(shared_from_this(), &m_collector);
193
9.30k
    default:
194
9.30k
      break;
195
21.8k
    }
196
21.8k
  }
197
198
12.8k
  return std::make_shared<FictionBook2SkipElementContext>(shared_from_this());
199
22.1k
}
200
201
void FictionBookGathererContext::endOfElement()
202
3.05k
{
203
3.05k
}
204
205
void FictionBookGathererContext::attribute(const FictionBook2TokenData &, const FictionBook2TokenData *, const char *)
206
78
{
207
78
}
208
209
DocumentContext::DocumentContext(FictionBook2Collector::NoteMap_t &notes, FictionBook2Collector::BinaryMap_t &bitmaps, librevenge::RVNGTextInterface *const document)
210
44.7k
  : FictionBook2ParserContext(nullptr)
211
44.7k
  , m_document(document)
212
44.7k
  , m_notes(notes)
213
44.7k
  , m_bitmaps(bitmaps)
214
44.7k
  , m_generating(document != nullptr)
215
44.7k
{
216
44.7k
}
217
218
std::shared_ptr<FictionBook2XMLParserContext> DocumentContext::element(const FictionBook2TokenData &name, const FictionBook2TokenData &ns)
219
9.00k
{
220
9.00k
  if ((FictionBook2Token::NS_FICTIONBOOK == getFictionBook2TokenID(ns)) && (FictionBook2Token::FictionBook == getFictionBook2TokenID(name)))
221
8.98k
  {
222
8.98k
    if (m_generating)
223
3.28k
      return std::make_shared<FictionBookGeneratorContext>(shared_from_this(), m_notes, m_bitmaps, m_document);
224
5.70k
    else
225
5.70k
      return std::make_shared<FictionBookGathererContext>(shared_from_this(), m_notes, m_bitmaps);
226
8.98k
  }
227
228
18
  return nullptr;
229
9.00k
}
230
231
std::shared_ptr<FictionBook2XMLParserContext> DocumentContext::leaveContext() const
232
0
{
233
0
  return nullptr;
234
0
}
235
236
void DocumentContext::startOfElement()
237
0
{
238
0
}
239
240
void DocumentContext::endOfElement()
241
0
{
242
0
}
243
244
void DocumentContext::attribute(const FictionBook2TokenData &, const FictionBook2TokenData *, const char *)
245
0
{
246
0
}
247
248
void DocumentContext::endOfAttributes()
249
0
{
250
0
}
251
252
void DocumentContext::text(const char *)
253
0
{
254
0
}
255
256
}
257
258
namespace
259
{
260
261
void processAttribute(std::shared_ptr<FictionBook2XMLParserContext> const context, const xmlTextReaderPtr reader)
262
159k
{
263
159k
  const FictionBook2TokenData *const name = getFictionBook2Token(xmlTextReaderConstLocalName(reader));
264
159k
  const xmlChar *const nsUri = xmlTextReaderConstNamespaceUri(reader);
265
159k
  const FictionBook2TokenData *const ns = nsUri ? getFictionBook2Token(nsUri) : nullptr;
266
159k
  if (name && (FictionBook2Token::NS_XMLNS != getFictionBook2TokenID(ns))) // ignore unknown attributes and namespace decls
267
111k
    context->attribute(*name, ns, reinterpret_cast<const char *>(xmlTextReaderConstValue(reader)));
268
159k
}
269
270
std::shared_ptr<FictionBook2XMLParserContext> processNode(std::shared_ptr<FictionBook2XMLParserContext> context, const xmlTextReaderPtr reader)
271
5.57M
{
272
5.57M
  std::shared_ptr<FictionBook2XMLParserContext> newContext = context;
273
5.57M
  switch (xmlTextReaderNodeType(reader))
274
5.57M
  {
275
2.85M
  case XML_READER_TYPE_ELEMENT :
276
2.85M
  {
277
2.85M
    const xmlChar *name_str = xmlTextReaderConstLocalName(reader);
278
2.85M
    const xmlChar *ns_str = xmlTextReaderConstNamespaceUri(reader);
279
280
2.85M
    const FictionBook2TokenData *name = name_str ? getFictionBook2Token(name_str) : nullptr;
281
2.85M
    const FictionBook2TokenData *ns = ns_str ? getFictionBook2Token(ns_str) : nullptr;
282
283
2.85M
    if (!name || !ns)
284
      // TODO: unknown elements should not be skipped entirely, but
285
      // their content should be processed as if they did not exist.
286
      // Unfortunately this would be quite hard (if not impossible) to
287
      // do in the current parser framework
288
1.04M
      newContext = std::make_shared<FictionBook2SkipElementContext>(std::dynamic_pointer_cast<FictionBook2ParserContext>(context));
289
1.81M
    else
290
1.81M
      newContext = context->element(*name, *ns);
291
292
2.85M
    if (newContext)
293
2.85M
    {
294
2.85M
      newContext->startOfElement();
295
2.85M
      const bool isEmpty = xmlTextReaderIsEmptyElement(reader);
296
297
2.85M
      if (xmlTextReaderHasAttributes(reader))
298
97.3k
      {
299
97.3k
        int ret = xmlTextReaderMoveToFirstAttribute(reader);
300
256k
        while (1 == ret)
301
159k
        {
302
159k
          processAttribute(newContext, reader);
303
159k
          ret = xmlTextReaderMoveToNextAttribute(reader);
304
159k
        }
305
97.3k
        if (0 > ret) // some error while reading
306
0
          newContext = nullptr;
307
97.3k
      }
308
2.85M
      if (newContext)
309
2.85M
        newContext->endOfAttributes();
310
311
2.85M
      if (newContext && isEmpty)
312
2.18M
      {
313
2.18M
        newContext->endOfElement();
314
2.18M
        newContext = newContext->leaveContext();
315
2.18M
      }
316
2.85M
    }
317
318
2.85M
    break;
319
0
  }
320
0
  case XML_READER_TYPE_ATTRIBUTE :
321
0
    assert(false && "How did i ever got there?");
322
0
    processAttribute(context, reader);
323
0
    break;
324
640k
  case XML_READER_TYPE_END_ELEMENT :
325
640k
  {
326
640k
    context->endOfElement();
327
640k
    newContext = context->leaveContext();
328
640k
    break;
329
0
  }
330
1.57M
  case XML_READER_TYPE_TEXT :
331
1.57M
  {
332
1.57M
    std::unique_ptr<xmlChar, decltype(xmlFree)> text(xmlTextReaderReadString(reader), xmlFree);
333
1.57M
    context->text(reinterpret_cast<char *>(text.get()));
334
1.57M
    break;
335
0
  }
336
501k
  default :
337
    // ignore other types of XML content
338
501k
    break;
339
5.57M
  }
340
341
5.57M
  return newContext;
342
5.57M
}
343
344
}
345
346
FictionBook2Parser::FictionBook2Parser(librevenge::RVNGInputStream *input)
347
25.9k
  : m_input(input)
348
25.9k
{
349
25.9k
  assert(m_input);
350
25.9k
}
351
352
bool FictionBook2Parser::parse(std::shared_ptr<FictionBook2XMLParserContext> const context) const
353
44.7k
{
354
44.7k
  m_input->seek(0, librevenge::RVNG_SEEK_SET);
355
356
44.7k
  const auto reader = makeXmlReader(m_input);
357
44.7k
  if (!reader)
358
0
    return false;
359
360
44.7k
  int ret = xmlTextReaderRead(reader.get());
361
44.7k
  std::shared_ptr<FictionBook2XMLParserContext> currentContext = context;
362
5.61M
  while ((1 == ret) && currentContext)
363
5.57M
  {
364
5.57M
    currentContext = processNode(currentContext, reader.get());
365
5.57M
    if (currentContext)
366
5.57M
      ret = xmlTextReaderRead(reader.get());
367
5.57M
  }
368
369
44.7k
  xmlTextReaderClose(reader.get());
370
371
  // we processed all input and it was valid
372
44.7k
  return (!currentContext || (currentContext == context)) && m_input->isEnd();
373
44.7k
}
374
375
bool FictionBook2Parser::parse(librevenge::RVNGTextInterface *const document) const
376
25.9k
{
377
25.9k
  FictionBook2Collector::NoteMap_t notes;
378
25.9k
  FictionBook2Collector::BinaryMap_t bitmaps;
379
380
25.9k
  {
381
    // in the 1st pass we gather notes and bitmaps
382
25.9k
    DocumentContext context(notes, bitmaps);
383
25.9k
    if (!parse(std::shared_ptr<DocumentContext>(&context, EBOOKDummyDeleter())))
384
7.08k
      return false;
385
25.9k
  }
386
387
18.8k
  DocumentContext context(notes, bitmaps, document);
388
18.8k
  return parse(std::shared_ptr<DocumentContext>(&context, EBOOKDummyDeleter()));
389
25.9k
}
390
391
}
392
393
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */