Tag Parser  7.0.3
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
genericfileelement.h
Go to the documentation of this file.
1 #ifndef TAG_PARSER_GENERICFILEELEMENT_H
2 #define TAG_PARSER_GENERICFILEELEMENT_H
3 
4 #include "./exceptions.h"
5 #include "./progressfeedback.h"
6 
7 #include <c++utilities/conversion/types.h>
8 #include <c++utilities/io/copy.h>
9 
10 #include <initializer_list>
11 #include <iostream>
12 #include <list>
13 #include <memory>
14 #include <string>
15 
16 namespace IoUtilities {
17 
18 class BinaryReader;
19 class BinaryWriter;
20 
21 } // namespace IoUtilities
22 
23 namespace TagParser {
24 
25 class Diagnostics;
26 
36 template <typename ImplementationType> class FileElementTraits {
37 };
38 
47 template <class ImplementationType> class TAG_PARSER_EXPORT GenericFileElement {
48  friend class FileElementTraits<ImplementationType>;
49 
50 public:
55 
60 
65 
66  GenericFileElement(ContainerType &container, uint64 startOffset);
67  GenericFileElement(ImplementationType &parent, uint64 startOffset);
68  GenericFileElement(ContainerType &container, uint64 startOffset, uint64 maxSize);
69  GenericFileElement(const GenericFileElement &other) = delete;
70  GenericFileElement(GenericFileElement &other) = delete;
71  GenericFileElement &operator=(const GenericFileElement &other) = delete;
72 
73  ContainerType &container();
74  const ContainerType &container() const;
75  std::iostream &stream();
76  IoUtilities::BinaryReader &reader();
77  IoUtilities::BinaryWriter &writer();
78  uint64 startOffset() const;
79  uint64 relativeStartOffset() const;
80  const IdentifierType &id() const;
81  std::string idToString() const;
82  uint32 idLength() const;
83  uint32 headerSize() const;
84  DataSizeType dataSize() const;
85  uint32 sizeLength() const;
86  uint64 dataOffset() const;
87  uint64 totalSize() const;
88  uint64 endOffset() const;
89  uint64 maxTotalSize() const;
90  byte level() const;
91  ImplementationType *parent();
92  const ImplementationType *parent() const;
93  ImplementationType *parent(byte n);
94  const ImplementationType *parent(byte n) const;
95  ImplementationType *nextSibling();
96  const ImplementationType *nextSibling() const;
97  ImplementationType *firstChild();
98  const ImplementationType *firstChild() const;
99  ImplementationType *lastChild();
100  const ImplementationType *lastChild() const;
101  ImplementationType *subelementByPath(Diagnostics &diag, IdentifierType item);
102  ImplementationType *subelementByPath(Diagnostics &diag, IdentifierType item, IdentifierType remainingPath...);
103  const ImplementationType *subelementByPath(Diagnostics &diag, IdentifierType item) const;
104  const ImplementationType *subelementByPath(Diagnostics &diag, IdentifierType item, IdentifierType remainingPath...) const;
105  ImplementationType *childById(const IdentifierType &id, Diagnostics &diag);
106  const ImplementationType *childById(const IdentifierType &id, Diagnostics &diag) const;
107  ImplementationType *siblingById(const IdentifierType &id, Diagnostics &diag);
108  const ImplementationType *siblingById(const IdentifierType &id, Diagnostics &diag) const;
109  ImplementationType *siblingByIdIncludingThis(const IdentifierType &id, Diagnostics &diag);
110  const ImplementationType *siblingByIdIncludingThis(const IdentifierType &id, Diagnostics &diag) const;
111  bool isParent() const;
112  bool isPadding() const;
113  uint64 firstChildOffset() const;
114  bool isParsed() const;
115  void clear();
116  void parse(Diagnostics &diag);
117  void reparse(Diagnostics &diag);
118  void validateSubsequentElementStructure(Diagnostics &diag, uint64 *paddingSize = nullptr);
119  static constexpr uint32 maximumIdLengthSupported();
120  static constexpr uint32 maximumSizeLengthSupported();
121  static constexpr byte minimumElementSize();
122  void copyHeader(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress);
123  void copyWithoutChilds(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress);
124  void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress);
125  void makeBuffer();
126  void discardBuffer();
127  void copyBuffer(std::ostream &targetStream);
128  void copyPreferablyFromBuffer(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress);
129  const std::unique_ptr<char[]> &buffer();
130  ImplementationType *denoteFirstChild(uint32 offset);
131 
132 protected:
135  uint64 m_maxSize;
137  uint32 m_idLength;
138  uint32 m_sizeLength;
139  ImplementationType *m_parent;
140  std::unique_ptr<ImplementationType> m_nextSibling;
141  std::unique_ptr<ImplementationType> m_firstChild;
142  std::unique_ptr<char[]> m_buffer;
143 
144 private:
145  void copyInternal(std::ostream &targetStream, uint64 startOffset, uint64 bytesToCopy, Diagnostics &diag, AbortableProgressFeedback *progress);
146 
147  ContainerType *m_container;
148  bool m_parsed;
149 
150 protected:
152 };
153 
158 template <class ImplementationType>
160  : m_id(IdentifierType())
161  , m_startOffset(startOffset)
162  , m_dataSize(0)
163  , m_idLength(0)
164  , m_sizeLength(0)
165  , m_parent(nullptr)
166  , m_container(&container)
167  , m_parsed(false)
168  , m_sizeUnknown(false)
169 {
170  m_maxSize = container.fileInfo().size();
171  if (m_maxSize > startOffset) {
172  m_maxSize -= startOffset;
173  stream().seekg(startOffset, std::ios_base::beg);
174  } else {
175  m_maxSize = 0;
176  }
177 }
178 
182 template <class ImplementationType>
183 GenericFileElement<ImplementationType>::GenericFileElement(ImplementationType &parent, uint64 startOffset)
184  : m_id(IdentifierType())
185  , m_startOffset(startOffset)
186  , m_maxSize(parent.startOffset() + parent.totalSize() - startOffset)
187  , m_dataSize(0)
188  , m_idLength(0)
189  , m_sizeLength(0)
190  , m_parent(&parent)
191  , m_container(&parent.container())
192  , m_parsed(false)
193  , m_sizeUnknown(false)
194 {
195 }
196 
200 template <class ImplementationType>
202  GenericFileElement<ImplementationType>::ContainerType &container, uint64 startOffset, uint64 maxSize)
203  : m_id(IdentifierType())
204  , m_startOffset(startOffset)
205  , m_maxSize(maxSize)
206  , m_dataSize(0)
207  , m_idLength(0)
208  , m_sizeLength(0)
209  , m_parent(nullptr)
210  , m_container(&container)
211  , m_parsed(false)
212  , m_sizeUnknown(false)
213 {
214 }
215 
219 template <class ImplementationType>
221 {
222  return *m_container;
223 }
224 
228 template <class ImplementationType>
230 {
231  return *m_container;
232 }
233 
237 template <class ImplementationType> inline std::iostream &GenericFileElement<ImplementationType>::stream()
238 {
239  return m_container->stream();
240 }
241 
245 template <class ImplementationType> inline IoUtilities::BinaryReader &GenericFileElement<ImplementationType>::reader()
246 {
247  return m_container->reader();
248 }
249 
253 template <class ImplementationType> inline IoUtilities::BinaryWriter &GenericFileElement<ImplementationType>::writer()
254 {
255  return m_container->writer();
256 }
257 
261 template <class ImplementationType> inline uint64 GenericFileElement<ImplementationType>::startOffset() const
262 {
263  return m_startOffset;
264 }
265 
269 template <class ImplementationType> inline uint64 GenericFileElement<ImplementationType>::relativeStartOffset() const
270 {
271  return parent() ? startOffset() - parent()->startOffset() : startOffset();
272 }
273 
277 template <class ImplementationType>
279 {
280  return m_id;
281 }
282 
286 template <class ImplementationType> inline std::string GenericFileElement<ImplementationType>::idToString() const
287 {
288  return static_cast<ImplementationType *>(this)->idToString();
289 }
290 
294 template <class ImplementationType> inline uint32 GenericFileElement<ImplementationType>::idLength() const
295 {
296  return m_idLength;
297 }
298 
304 template <class ImplementationType> inline uint32 GenericFileElement<ImplementationType>::headerSize() const
305 {
306  return m_idLength + m_sizeLength;
307 }
308 
314 template <class ImplementationType>
316 {
317  return m_dataSize;
318 }
319 
323 template <class ImplementationType> inline uint32 GenericFileElement<ImplementationType>::sizeLength() const
324 {
325  return m_sizeLength;
326 }
327 
333 template <class ImplementationType> inline uint64 GenericFileElement<ImplementationType>::dataOffset() const
334 {
335  return startOffset() + headerSize();
336 }
337 
343 template <class ImplementationType> inline uint64 GenericFileElement<ImplementationType>::totalSize() const
344 {
345  return headerSize() + dataSize();
346 }
347 
351 template <class ImplementationType> inline uint64 GenericFileElement<ImplementationType>::endOffset() const
352 {
353  return startOffset() + totalSize();
354 }
355 
362 template <class ImplementationType> inline uint64 GenericFileElement<ImplementationType>::maxTotalSize() const
363 {
364  return m_maxSize;
365 }
366 
371 template <class ImplementationType> byte GenericFileElement<ImplementationType>::level() const
372 {
373  byte level = 0;
374  for (const ImplementationType *parent = m_parent; parent; ++level, parent = parent->m_parent)
375  ;
376  return level;
377 }
378 
385 template <class ImplementationType> inline ImplementationType *GenericFileElement<ImplementationType>::parent()
386 {
387  return m_parent;
388 }
389 
396 template <class ImplementationType> inline const ImplementationType *GenericFileElement<ImplementationType>::parent() const
397 {
398  return m_parent;
399 }
400 
407 template <class ImplementationType> ImplementationType *GenericFileElement<ImplementationType>::parent(byte n)
408 {
409  ImplementationType *parent = static_cast<ImplementationType *>(this);
410  for (; n && parent; --n, parent = parent->m_parent)
411  ;
412  return parent;
413 }
414 
421 template <class ImplementationType> inline const ImplementationType *GenericFileElement<ImplementationType>::parent(byte n) const
422 {
423  return const_cast<GenericFileElement<ImplementationType> *>(this)->parent(n);
424 }
425 
434 template <class ImplementationType> inline ImplementationType *GenericFileElement<ImplementationType>::nextSibling()
435 {
436  return m_nextSibling.get();
437 }
438 
447 template <class ImplementationType> inline const ImplementationType *GenericFileElement<ImplementationType>::nextSibling() const
448 {
449  return m_nextSibling.get();
450 }
451 
460 template <class ImplementationType> inline ImplementationType *GenericFileElement<ImplementationType>::firstChild()
461 {
462  return m_firstChild.get();
463 }
464 
473 template <class ImplementationType> inline const ImplementationType *GenericFileElement<ImplementationType>::firstChild() const
474 {
475  return m_firstChild.get();
476 }
477 
486 template <class ImplementationType> inline ImplementationType *GenericFileElement<ImplementationType>::lastChild()
487 {
488  for (ImplementationType *child = firstChild(); child; child = child->nextSibling()) {
489  if (!child->m_nextSibling) {
490  return child;
491  }
492  }
493  return nullptr;
494 }
495 
504 template <class ImplementationType> inline const ImplementationType *GenericFileElement<ImplementationType>::lastChild() const
505 {
506  return const_cast<GenericFileElement<ImplementationType> *>(this)->lastChild();
507 }
508 
518 template <class ImplementationType>
520 {
521  // ensure element is parsed
522  parse(diag);
523  // return the element if it matches the current and last item in the path
524  if (item == id()) {
525  return static_cast<ImplementationType *>(this);
526  }
527  // check whether a sibling matches the item
528  if (nextSibling()) {
529  return nextSibling()->subelementByPath(diag, item);
530  }
531  return nullptr;
532 }
533 
543 template <class ImplementationType>
545 {
546  // ensure element is parsed
547  parse(diag);
548  // continue with next item in path if the element matches the current item
549  if (item == id()) {
550  if (!firstChild()) {
551  return nullptr;
552  }
553  return firstChild()->subelementByPath(diag, remainingPath);
554  }
555  // check whether a sibling matches the current item
556  if (nextSibling()) {
557  return nextSibling()->subelementByPath(diag, item, remainingPath);
558  }
559  return nullptr;
560 }
561 
571 template <class ImplementationType>
573 {
574  return const_cast<GenericFileElement<ImplementationType> *>(this)->subelementByPath(diag, item);
575 }
576 
586 template <class ImplementationType>
588  Diagnostics &diag, IdentifierType item, IdentifierType remainingPath...) const
589 {
590  return const_cast<GenericFileElement<ImplementationType> *>(this)->subelementByPath(diag, item, remainingPath);
591 }
592 
602 template <class ImplementationType> ImplementationType *GenericFileElement<ImplementationType>::childById(const IdentifierType &id, Diagnostics &diag)
603 {
604  parse(diag); // ensure element is parsed
605  for (ImplementationType *child = firstChild(); child; child = child->nextSibling()) {
606  child->parse(diag);
607  if (child->id() == id) {
608  return child;
609  }
610  }
611  return nullptr;
612 }
613 
623 template <class ImplementationType>
624 const ImplementationType *GenericFileElement<ImplementationType>::childById(const IdentifierType &id, Diagnostics &diag) const
625 {
626  return const_cast<GenericFileElement<ImplementationType> *>(this)->childById(id, diag);
627 }
628 
639 template <class ImplementationType>
641 {
642  parse(diag); // ensure element is parsed
643  for (ImplementationType *sibling = nextSibling(); sibling; sibling = sibling->nextSibling()) {
644  sibling->parse(diag);
645  if (sibling->id() == id) {
646  return sibling;
647  }
648  }
649  return nullptr;
650 }
651 
662 template <class ImplementationType>
663 const ImplementationType *GenericFileElement<ImplementationType>::siblingById(const IdentifierType &id, Diagnostics &diag) const
664 {
665  return const_cast<GenericFileElement<ImplementationType> *>(this)->siblingById(id, diag);
666 }
667 
678 template <class ImplementationType>
680 {
681  parse(diag); // ensure element is parsed
682  for (ImplementationType *sibling = static_cast<ImplementationType *>(this); sibling; sibling = sibling->nextSibling()) {
683  sibling->parse(diag);
684  if (sibling->id() == id) {
685  return sibling;
686  }
687  }
688  return nullptr;
689 }
690 
701 template <class ImplementationType>
703 {
704  return const_cast<GenericFileElement<ImplementationType> *>(this)->siblingByIdIncludingThis(id, diag);
705 }
706 
710 template <class ImplementationType> inline bool GenericFileElement<ImplementationType>::isParent() const
711 {
712  return static_cast<const ImplementationType *>(this)->isParent();
713 }
714 
718 template <class ImplementationType> inline bool GenericFileElement<ImplementationType>::isPadding() const
719 {
720  return static_cast<const ImplementationType *>(this)->isPadding();
721 }
722 
726 template <class ImplementationType> inline uint64 GenericFileElement<ImplementationType>::firstChildOffset() const
727 {
728  return static_cast<const ImplementationType *>(this)->firstChildOffset();
729 }
730 
734 template <class ImplementationType> inline bool GenericFileElement<ImplementationType>::isParsed() const
735 {
736  return m_parsed;
737 }
738 
745 template <class ImplementationType> void GenericFileElement<ImplementationType>::clear()
746 {
747  m_id = IdentifierType();
748  //m_startOffset = 0;
749  m_idLength = 0;
750  m_dataSize = 0;
751  m_sizeLength = 0;
752  m_nextSibling = nullptr;
753  m_firstChild = nullptr;
754  m_parsed = false;
755 }
756 
771 template <class ImplementationType> void GenericFileElement<ImplementationType>::parse(Diagnostics &diag)
772 {
773  if (!m_parsed) {
774  static_cast<ImplementationType *>(this)->internalParse(diag);
775  m_parsed = true;
776  }
777 }
778 
795 template <class ImplementationType> void GenericFileElement<ImplementationType>::reparse(Diagnostics &diag)
796 {
797  clear();
798  static_cast<ImplementationType *>(this)->parse(diag);
799  m_parsed = true;
800 }
801 
814 template <class ImplementationType>
816 {
817  // validate element itself by just parsing it
818  parse(diag);
819  // validate children
820  if (firstChild()) {
821  try {
822  firstChild()->validateSubsequentElementStructure(diag, paddingSize);
823  } catch (const Failure &) {
824  // ignore critical errors in child structure to continue validating siblings
825  // (critical notifications about the errors should have already been added to diag, so nothing to do)
826  }
827  } else if (paddingSize && isPadding()) { // element is padding
828  *paddingSize += totalSize();
829  }
830  // validate siblings
831  if (nextSibling()) {
832  nextSibling()->validateSubsequentElementStructure(diag, paddingSize);
833  }
834 }
835 
839 template <class ImplementationType>
841 {
842  copyInternal(targetStream, startOffset(), headerSize(), diag, progress);
843 }
844 
848 template <class ImplementationType>
850 {
851  if (uint32 firstChildOffset = this->firstChildOffset()) {
852  copyInternal(targetStream, startOffset(), firstChildOffset, diag, progress);
853  } else {
854  copyInternal(targetStream, startOffset(), totalSize(), diag, progress);
855  }
856 }
857 
861 template <class ImplementationType>
863 {
864  copyInternal(targetStream, startOffset(), totalSize(), diag, progress);
865 }
866 
871 template <class ImplementationType> void GenericFileElement<ImplementationType>::makeBuffer()
872 {
873  m_buffer = std::make_unique<char[]>(totalSize());
874  container().stream().seekg(startOffset());
875  container().stream().read(m_buffer.get(), totalSize());
876 }
877 
881 template <class ImplementationType> inline void GenericFileElement<ImplementationType>::discardBuffer()
882 {
883  m_buffer.reset();
884 }
885 
890 template <class ImplementationType> inline void GenericFileElement<ImplementationType>::copyBuffer(std::ostream &targetStream)
891 {
892  targetStream.write(m_buffer.get(), totalSize());
893 }
894 
899 template <class ImplementationType>
901  std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
902 {
903  m_buffer ? copyBuffer(targetStream) : copyEntirely(targetStream, diag, progress);
904 }
905 
910 template <class ImplementationType> inline const std::unique_ptr<char[]> &GenericFileElement<ImplementationType>::buffer()
911 {
912  return m_buffer;
913 }
914 
922 template <class ImplementationType>
924  std::ostream &targetStream, uint64 startOffset, uint64 bytesToCopy, Diagnostics &diag, AbortableProgressFeedback *progress)
925 {
926  // ensure the header has been parsed correctly
927  try {
928  parse(diag);
929  } catch (const Failure &) {
930  throw InvalidDataException();
931  }
932  auto &stream = container().stream();
933  stream.seekg(startOffset);
935  if (progress) {
936  copyHelper.callbackCopy(stream, targetStream, bytesToCopy, std::bind(&AbortableProgressFeedback::isAborted, std::ref(progress)),
937  std::bind(&AbortableProgressFeedback::updateStepPercentageFromFraction, std::ref(progress), std::placeholders::_1));
938  } else {
939  copyHelper.copy(stream, targetStream, bytesToCopy);
940  }
941 }
942 
947 template <class ImplementationType> ImplementationType *GenericFileElement<ImplementationType>::denoteFirstChild(uint32 relativeFirstChildOffset)
948 {
949  if (relativeFirstChildOffset + minimumElementSize() <= totalSize()) {
950  m_firstChild.reset(new ImplementationType(static_cast<ImplementationType &>(*this), startOffset() + relativeFirstChildOffset));
951  } else {
952  m_firstChild.reset();
953  }
954  return m_firstChild.get();
955 }
956 
960 template <class ImplementationType> constexpr uint32 GenericFileElement<ImplementationType>::maximumIdLengthSupported()
961 {
962  return sizeof(IdentifierType);
963 }
964 
968 template <class ImplementationType> constexpr uint32 GenericFileElement<ImplementationType>::maximumSizeLengthSupported()
969 {
970  return sizeof(DataSizeType);
971 }
972 
976 template <class ImplementationType> constexpr byte GenericFileElement<ImplementationType>::minimumElementSize()
977 {
979 }
980 
995 } // namespace TagParser
996 
997 #endif // TAG_PARSER_GENERICFILEELEMENT_H
FileElementTraits< ImplementationType >::ContainerType ContainerType
Specifies the type of the corresponding container.
std::unique_ptr< char[]> m_buffer
std::unique_ptr< ImplementationType > m_firstChild
std::unique_ptr< ImplementationType > m_nextSibling
uint64 startOffset() const
Returns the start offset in the related stream.
IoUtilities::BinaryReader & reader()
Returns the related BinaryReader.
Contains utility classes helping to read and write streams.
GenericFileElement(ContainerType &container, uint64 startOffset)
IoUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
std::iostream & stream()
Returns the related stream.
FileElementTraits< ImplementationType >::IdentifierType IdentifierType
Specifies the type used to store identifiers.
#define TAG_PARSER_EXPORT
Marks the symbol to be exported by the tagparser library.
FileElementTraits< ImplementationType >::DataSizeType DataSizeType
Specifies the type used to store data sizes.