Coverage Report

Created: 2026-04-29 07:28

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
57.9k
{
31
57.9k
  const unsigned char magic[] =
32
57.9k
  {
33
57.9k
    0x56, 0x69, 0x73, 0x69, 0x6f, 0x20, 0x28, 0x54, 0x4d, 0x29,
34
57.9k
    0x20, 0x44, 0x72, 0x61, 0x77, 0x69, 0x6e, 0x67, 0x0d, 0x0a,
35
57.9k
    0x0
36
57.9k
  };
37
57.9k
  auto startPosition = (int)input->tell();
38
57.9k
  unsigned long numBytesRead = 0;
39
57.9k
  const unsigned char *buffer = input->read(VSD_NUM_ELEMENTS(magic), numBytesRead);
40
57.9k
  const bool returnValue = VSD_NUM_ELEMENTS(magic) == numBytesRead
41
54.4k
                           && std::equal(magic, magic + VSD_NUM_ELEMENTS(magic), buffer);
42
57.9k
  input->seek(startPosition, librevenge::RVNG_SEEK_SET);
43
57.9k
  return returnValue;
44
57.9k
}
45
46
57.9k
static bool isBinaryVisioDocument(librevenge::RVNGInputStream *input) try
47
57.9k
{
48
57.9k
  std::shared_ptr<librevenge::RVNGInputStream> docStream;
49
57.9k
  input->seek(0, librevenge::RVNG_SEEK_SET);
50
57.9k
  if (input->isStructured())
51
4.04k
  {
52
4.04k
    input->seek(0, librevenge::RVNG_SEEK_SET);
53
4.04k
    docStream.reset(input->getSubStreamByName("VisioDocument"));
54
4.04k
  }
55
57.9k
  if (!docStream)
56
56.9k
    docStream.reset(input, libvisio::VSDDummyDeleter());
57
58
57.9k
  docStream->seek(0, librevenge::RVNG_SEEK_SET);
59
57.9k
  unsigned char version = 0;
60
57.9k
  if (checkVisioMagic(docStream.get()))
61
15.9k
  {
62
15.9k
    docStream->seek(0x1A, librevenge::RVNG_SEEK_SET);
63
15.9k
    version = libvisio::readU8(docStream.get());
64
15.9k
  }
65
57.9k
  input->seek(0, librevenge::RVNG_SEEK_SET);
66
67
57.9k
  VSD_DEBUG_MSG(("VisioDocument: version %i\n", version));
68
69
  // Versions 2k (6) and 2k3 (11)
70
57.9k
  return ((version >= 1 && version <= 6) || version == 11);
71
57.9k
}
72
57.9k
catch (...)
73
57.9k
{
74
2
  return false;
75
2
}
76
77
15.9k
static bool parseBinaryVisioDocument(librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter, bool isStencilExtraction) try
78
15.9k
{
79
15.9k
  VSD_DEBUG_MSG(("Parsing Binary Visio Document\n"));
80
15.9k
  input->seek(0, librevenge::RVNG_SEEK_SET);
81
15.9k
  std::shared_ptr<librevenge::RVNGInputStream> docStream;
82
15.9k
  if (input->isStructured())
83
783
    docStream.reset(input->getSubStreamByName("VisioDocument"));
84
15.9k
  if (!docStream)
85
15.1k
    docStream.reset(input, libvisio::VSDDummyDeleter());
86
87
15.9k
  docStream->seek(0x1A, librevenge::RVNG_SEEK_SET);
88
89
15.9k
  std::unique_ptr<libvisio::VSDParser> parser;
90
91
15.9k
  unsigned char version = libvisio::readU8(docStream.get());
92
15.9k
  switch (version)
93
15.9k
  {
94
158
  case 1:
95
241
  case 2:
96
731
  case 3:
97
4.26k
  case 4:
98
8.86k
  case 5:
99
8.86k
    parser.reset(new libvisio::VSD5Parser(docStream.get(), painter));
100
8.86k
    break;
101
3.54k
  case 6:
102
3.54k
    parser.reset(new libvisio::VSD6Parser(docStream.get(), painter));
103
3.54k
    break;
104
3.56k
  case 11:
105
3.56k
    parser.reset(new libvisio::VSDParser(docStream.get(), painter, input));
106
3.56k
    break;
107
0
  default:
108
0
    break;
109
15.9k
  }
110
111
15.9k
  if (isStencilExtraction)
112
0
    return parser->extractStencils();
113
15.9k
  else
114
15.9k
    return parser->parseMain();
115
15.9k
}
116
15.9k
catch (...)
117
15.9k
{
118
24
  return false;
119
24
}
120
121
41.9k
static bool isOpcVisioDocument(librevenge::RVNGInputStream *input) try
122
41.9k
{
123
41.9k
  input->seek(0, librevenge::RVNG_SEEK_SET);
124
41.9k
  if (!input->isStructured())
125
38.7k
    return false;
126
127
3.25k
  std::unique_ptr<librevenge::RVNGInputStream> tmpInput(input->getSubStreamByName("_rels/.rels"));
128
3.25k
  if (!tmpInput)
129
1.55k
    return false;
130
131
1.70k
  libvisio::VSDXRelationships rootRels(tmpInput.get());
132
133
  // Check whether the relationship points to a Visio document stream
134
1.70k
  const libvisio::VSDXRelationship *rel = rootRels.getRelationshipByType("http://schemas.microsoft.com/visio/2010/relationships/document");
135
1.70k
  if (!rel)
136
61
    return false;
137
138
  // check whether the pointed Visio document stream exists in the document
139
1.64k
  tmpInput.reset(input->getSubStreamByName(rel->getTarget().c_str()));
140
1.64k
  return bool(tmpInput);
141
1.70k
}
142
41.9k
catch (...)
143
41.9k
{
144
0
  return false;
145
0
}
146
147
1.56k
static bool parseOpcVisioDocument(librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter, bool isStencilExtraction) try
148
1.56k
{
149
1.56k
  VSD_DEBUG_MSG(("Parsing Visio Document based on Open Packaging Convention\n"));
150
1.56k
  input->seek(0, librevenge::RVNG_SEEK_SET);
151
1.56k
  libvisio::VSDXParser parser(input, painter);
152
1.56k
  if (isStencilExtraction && parser.extractStencils())
153
0
    return true;
154
1.56k
  else if (!isStencilExtraction && parser.parseMain())
155
1.23k
    return true;
156
334
  return false;
157
1.56k
}
158
1.56k
catch (...)
159
1.56k
{
160
0
  return false;
161
0
}
162
163
40.4k
static bool isXmlVisioDocument(librevenge::RVNGInputStream *input) try
164
40.4k
{
165
40.4k
  input->seek(0, librevenge::RVNG_SEEK_SET);
166
40.4k
  auto reader = libvisio::xmlReaderForStream(input);
167
40.4k
  if (!reader)
168
0
    return false;
169
40.4k
  int ret = xmlTextReaderRead(reader.get());
170
50.8k
  while (ret == 1 && 1 != xmlTextReaderNodeType(reader.get()))
171
10.4k
    ret = xmlTextReaderRead(reader.get());
172
40.4k
  if (ret != 1)
173
22.4k
  {
174
22.4k
    return false;
175
22.4k
  }
176
17.9k
  const xmlChar *name = xmlTextReaderConstName(reader.get());
177
17.9k
  if (!name)
178
0
  {
179
0
    return false;
180
0
  }
181
17.9k
  if (!xmlStrEqual(name, BAD_CAST("VisioDocument")))
182
680
  {
183
680
    return false;
184
680
  }
185
17.2k
  return true;
186
17.9k
}
187
40.4k
catch (...)
188
40.4k
{
189
0
  return false;
190
0
}
191
192
17.2k
static bool parseXmlVisioDocument(librevenge::RVNGInputStream *input, librevenge::RVNGDrawingInterface *painter, bool isStencilExtraction) try
193
17.2k
{
194
17.2k
  VSD_DEBUG_MSG(("Parsing Visio DrawingML Document\n"));
195
17.2k
  input->seek(0, librevenge::RVNG_SEEK_SET);
196
17.2k
  libvisio::VDXParser parser(input, painter);
197
17.2k
  if (isStencilExtraction && parser.extractStencils())
198
0
    return true;
199
17.2k
  else if (!isStencilExtraction && parser.parseMain())
200
15.1k
    return true;
201
2.06k
  return false;
202
17.2k
}
203
17.2k
catch (...)
204
17.2k
{
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
57.9k
{
241
57.9k
  if (!input || !painter)
242
0
    return false;
243
244
57.9k
  if (isBinaryVisioDocument(input))
245
15.9k
  {
246
15.9k
    if (parseBinaryVisioDocument(input, painter, false))
247
13.5k
      return true;
248
2.40k
    return false;
249
15.9k
  }
250
41.9k
  if (isOpcVisioDocument(input))
251
1.56k
  {
252
1.56k
    if (parseOpcVisioDocument(input, painter, false))
253
1.23k
      return true;
254
334
    return false;
255
1.56k
  }
256
40.4k
  if (isXmlVisioDocument(input))
257
17.2k
  {
258
17.2k
    if (parseXmlVisioDocument(input, painter, false))
259
15.1k
      return true;
260
2.06k
    return false;
261
17.2k
  }
262
23.1k
  return false;
263
40.4k
}
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: */