Coverage Report

Created: 2026-09-06 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libvisio/src/lib/VisioDocument.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 libvisio 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 <libvisio/libvisio.h>
11
12
#include <algorithm>
13
#include <memory>
14
#include <string>
15
16
#include <librevenge/librevenge.h>
17
#include "libvisio_utils.h"
18
#include "libvisio_xml.h"
19
#include "VDXParser.h"
20
#include "VSDParser.h"
21
#include "VSDXParser.h"
22
#include "VSD5Parser.h"
23
#include "VSD6Parser.h"
24
#include "VSDXMLHelper.h"
25
26
namespace
27
{
28
29
static bool checkVisioMagic(librevenge::RVNGInputStream *input)
30
23.3k
{
31
23.3k
  const unsigned char magic[] =
32
23.3k
  {
33
23.3k
    0x56, 0x69, 0x73, 0x69, 0x6f, 0x20, 0x28, 0x54, 0x4d, 0x29,
34
23.3k
    0x20, 0x44, 0x72, 0x61, 0x77, 0x69, 0x6e, 0x67, 0x0d, 0x0a,
35
23.3k
    0x0
36
23.3k
  };
37
23.3k
  auto startPosition = (int)input->tell();
38
23.3k
  unsigned long numBytesRead = 0;
39
23.3k
  const unsigned char *buffer = input->read(VSD_NUM_ELEMENTS(magic), numBytesRead);
40
23.3k
  const bool returnValue = VSD_NUM_ELEMENTS(magic) == numBytesRead
41
21.9k
                           && std::equal(magic, magic + VSD_NUM_ELEMENTS(magic), buffer);
42
23.3k
  input->seek(startPosition, librevenge::RVNG_SEEK_SET);
43
23.3k
  return returnValue;
44
23.3k
}
45
46
23.3k
static bool isBinaryVisioDocument(librevenge::RVNGInputStream *input) try
47
23.3k
{
48
23.3k
  std::shared_ptr<librevenge::RVNGInputStream> docStream;
49
23.3k
  input->seek(0, librevenge::RVNG_SEEK_SET);
50
23.3k
  if (input->isStructured())
51
2.62k
  {
52
2.62k
    input->seek(0, librevenge::RVNG_SEEK_SET);
53
2.62k
    docStream.reset(input->getSubStreamByName("VisioDocument"));
54
2.62k
  }
55
23.3k
  if (!docStream)
56
22.3k
    docStream.reset(input, libvisio::VSDDummyDeleter());
57
58
23.3k
  docStream->seek(0, librevenge::RVNG_SEEK_SET);
59
23.3k
  unsigned char version = 0;
60
23.3k
  if (checkVisioMagic(docStream.get()))
61
9.69k
  {
62
9.69k
    docStream->seek(0x1A, librevenge::RVNG_SEEK_SET);
63
9.69k
    version = libvisio::readU8(docStream.get());
64
9.69k
  }
65
23.3k
  input->seek(0, librevenge::RVNG_SEEK_SET);
66
67
23.3k
  VSD_DEBUG_MSG(("VisioDocument: version %i\n", version));
68
69
  // Versions 2k (6) and 2k3 (11)
70
23.3k
  return ((version >= 1 && version <= 6) || version == 11);
71
23.3k
}
72
23.3k
catch (...)
73
23.3k
{
74
1
  return false;
75
1
}
76
77
9.69k
static bool parseBinaryVisioDocument(librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter, bool isStencilExtraction) try
78
9.69k
{
79
9.69k
  VSD_DEBUG_MSG(("Parsing Binary Visio Document\n"));
80
9.69k
  input->seek(0, librevenge::RVNG_SEEK_SET);
81
9.69k
  std::shared_ptr<librevenge::RVNGInputStream> docStream;
82
9.69k
  if (input->isStructured())
83
864
    docStream.reset(input->getSubStreamByName("VisioDocument"));
84
9.69k
  if (!docStream)
85
8.82k
    docStream.reset(input, libvisio::VSDDummyDeleter());
86
87
9.69k
  docStream->seek(0x1A, librevenge::RVNG_SEEK_SET);
88
89
9.69k
  std::unique_ptr<libvisio::VSDParser> parser;
90
91
9.69k
  unsigned char version = libvisio::readU8(docStream.get());
92
9.69k
  switch (version)
93
9.69k
  {
94
13
  case 1:
95
91
  case 2:
96
451
  case 3:
97
1.81k
  case 4:
98
5.41k
  case 5:
99
5.41k
    parser.reset(new libvisio::VSD5Parser(docStream.get(), painter));
100
5.41k
    break;
101
2.08k
  case 6:
102
2.08k
    parser.reset(new libvisio::VSD6Parser(docStream.get(), painter));
103
2.08k
    break;
104
2.19k
  case 11:
105
2.19k
    parser.reset(new libvisio::VSDParser(docStream.get(), painter, input));
106
2.19k
    break;
107
0
  default:
108
0
    break;
109
9.69k
  }
110
111
9.69k
  if (isStencilExtraction)
112
0
    return parser->extractStencils();
113
9.69k
  else
114
9.69k
    return parser->parseMain();
115
9.69k
}
116
9.69k
catch (...)
117
9.69k
{
118
14
  return false;
119
14
}
120
121
13.6k
static bool isOpcVisioDocument(librevenge::RVNGInputStream *input) try
122
13.6k
{
123
13.6k
  input->seek(0, librevenge::RVNG_SEEK_SET);
124
13.6k
  if (!input->isStructured())
125
11.9k
    return false;
126
127
1.75k
  std::unique_ptr<librevenge::RVNGInputStream> tmpInput(input->getSubStreamByName("_rels/.rels"));
128
1.75k
  if (!tmpInput)
129
874
    return false;
130
131
884
  libvisio::VSDXRelationships rootRels(tmpInput.get());
132
133
  // Check whether the relationship points to a Visio document stream
134
884
  const libvisio::VSDXRelationship *rel = rootRels.getRelationshipByType("http://schemas.microsoft.com/visio/2010/relationships/document");
135
884
  if (!rel)
136
28
    return false;
137
138
  // check whether the pointed Visio document stream exists in the document
139
856
  tmpInput.reset(input->getSubStreamByName(rel->getTarget().c_str()));
140
856
  return bool(tmpInput);
141
884
}
142
13.6k
catch (...)
143
13.6k
{
144
0
  return false;
145
0
}
146
147
821
static bool parseOpcVisioDocument(librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter, bool isStencilExtraction) try
148
821
{
149
821
  VSD_DEBUG_MSG(("Parsing Visio Document based on Open Packaging Convention\n"));
150
821
  input->seek(0, librevenge::RVNG_SEEK_SET);
151
821
  libvisio::VSDXParser parser(input, painter);
152
821
  if (isStencilExtraction && parser.extractStencils())
153
0
    return true;
154
821
  else if (!isStencilExtraction && parser.parseMain())
155
658
    return true;
156
163
  return false;
157
821
}
158
821
catch (...)
159
821
{
160
0
  return false;
161
0
}
162
163
12.8k
static bool isXmlVisioDocument(librevenge::RVNGInputStream *input) try
164
12.8k
{
165
12.8k
  input->seek(0, librevenge::RVNG_SEEK_SET);
166
12.8k
  auto reader = libvisio::xmlReaderForStream(input);
167
12.8k
  if (!reader)
168
0
    return false;
169
12.8k
  int ret = xmlTextReaderRead(reader.get());
170
20.3k
  while (ret == 1 && 1 != xmlTextReaderNodeType(reader.get()))
171
7.51k
    ret = xmlTextReaderRead(reader.get());
172
12.8k
  if (ret != 1)
173
7.97k
  {
174
7.97k
    return false;
175
7.97k
  }
176
4.87k
  const xmlChar *name = xmlTextReaderConstName(reader.get());
177
4.87k
  if (!name)
178
0
  {
179
0
    return false;
180
0
  }
181
4.87k
  if (!xmlStrEqual(name, BAD_CAST("VisioDocument")))
182
450
  {
183
450
    return false;
184
450
  }
185
4.42k
  return true;
186
4.87k
}
187
12.8k
catch (...)
188
12.8k
{
189
0
  return false;
190
0
}
191
192
4.42k
static bool parseXmlVisioDocument(librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter, bool isStencilExtraction) try
193
4.42k
{
194
4.42k
  VSD_DEBUG_MSG(("Parsing Visio DrawingML Document\n"));
195
4.42k
  input->seek(0, librevenge::RVNG_SEEK_SET);
196
4.42k
  libvisio::VDXParser parser(input, painter);
197
4.42k
  if (isStencilExtraction && parser.extractStencils())
198
0
    return true;
199
4.42k
  else if (!isStencilExtraction && parser.parseMain())
200
3.93k
    return true;
201
488
  return false;
202
4.42k
}
203
4.42k
catch (...)
204
4.42k
{
205
0
  return false;
206
0
}
207
208
} // anonymous namespace
209
210
211
/**
212
Analyzes the content of an input stream to see if it can be parsed
213
\param input The input stream
214
\return A value that indicates whether the content from the input
215
stream is a Visio Document that libvisio able to parse
216
*/
217
VSDAPI bool libvisio::VisioDocument::isSupported(librevenge::RVNGInputStream *input)
218
0
{
219
0
  if (!input)
220
0
    return false;
221
222
0
  if (isBinaryVisioDocument(input))
223
0
    return true;
224
0
  if (isOpcVisioDocument(input))
225
0
    return true;
226
0
  if (isXmlVisioDocument(input))
227
0
    return true;
228
0
  return false;
229
0
}
230
231
/**
232
Parses the input stream content. It will make callbacks to the functions provided by a
233
librevenge::RVNGDrawingInterface class implementation when needed. This is often commonly called the
234
'main parsing routine'.
235
\param input The input stream
236
\param painter A WPGPainterInterface implementation
237
\return A value that indicates whether the parsing was successful
238
*/
239
VSDAPI bool libvisio::VisioDocument::parse(librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter)
240
23.3k
{
241
23.3k
  if (!input || !painter)
242
0
    return false;
243
244
23.3k
  if (isBinaryVisioDocument(input))
245
9.69k
  {
246
9.69k
    if (parseBinaryVisioDocument(input, painter, false))
247
8.34k
      return true;
248
1.34k
    return false;
249
9.69k
  }
250
13.6k
  if (isOpcVisioDocument(input))
251
821
  {
252
821
    if (parseOpcVisioDocument(input, painter, false))
253
658
      return true;
254
163
    return false;
255
821
  }
256
12.8k
  if (isXmlVisioDocument(input))
257
4.42k
  {
258
4.42k
    if (parseXmlVisioDocument(input, painter, false))
259
3.93k
      return true;
260
488
    return false;
261
4.42k
  }
262
8.42k
  return false;
263
12.8k
}
264
265
/**
266
Parses the input stream content and extracts stencil pages, one stencil page per output page.
267
It will make callbacks to the functions provided by a librevenge::RVNGDrawingInterface class implementation
268
when needed.
269
\param input The input stream
270
\param painter A WPGPainterInterface implementation
271
\return A value that indicates whether the parsing was successful
272
*/
273
VSDAPI bool libvisio::VisioDocument::parseStencils(librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter)
274
0
{
275
0
  if (!input || !painter)
276
0
    return false;
277
278
0
  if (isBinaryVisioDocument(input))
279
0
  {
280
0
    if (parseBinaryVisioDocument(input, painter, true))
281
0
      return true;
282
0
    return false;
283
0
  }
284
0
  if (isOpcVisioDocument(input))
285
0
  {
286
0
    if (parseOpcVisioDocument(input, painter, true))
287
0
      return true;
288
0
    return false;
289
0
  }
290
0
  if (isXmlVisioDocument(input))
291
0
  {
292
0
    if (parseXmlVisioDocument(input, painter, true))
293
0
      return true;
294
0
    return false;
295
0
  }
296
0
  return false;
297
0
}
298
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */