git-svn-id: https://matroska.svn.sourceforge.net/svnroot/matroska/trunk/libebml@14 a6f86f6d-0131-4f8e-9e7b-e335508773d5
529 lines
14 KiB
C++
529 lines
14 KiB
C++
/****************************************************************************
|
|
** libebml : parse EBML files, see http://embl.sourceforge.net/
|
|
**
|
|
** <file/class description>
|
|
**
|
|
** Copyright (C) 2002-2005 Steve Lhomme. All rights reserved.
|
|
**
|
|
** This file is part of libebml.
|
|
**
|
|
** This library is free software; you can redistribute it and/or
|
|
** modify it under the terms of the GNU Lesser General Public
|
|
** License as published by the Free Software Foundation; either
|
|
** version 2.1 of the License, or (at your option) any later version.
|
|
**
|
|
** This library is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
** Lesser General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU Lesser General Public
|
|
** License along with this library; if not, write to the Free Software
|
|
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
**
|
|
** See http://www.matroska.org/license/lgpl/ for LGPL licensing information.
|
|
**
|
|
** Contact license@matroska.org if any conditions of this licensing are
|
|
** not clear to you.
|
|
**
|
|
**********************************************************************/
|
|
|
|
/*!
|
|
\file
|
|
\version \$Id: EbmlMaster.cpp 1178 2005-05-19 15:47:11Z robux4 $
|
|
\author Steve Lhomme <robux4 @ users.sf.net>
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
|
|
#include "ebml/EbmlMaster.h"
|
|
#include "ebml/EbmlStream.h"
|
|
#include "ebml/EbmlContexts.h"
|
|
#include "ebml/MemIOCallback.h"
|
|
|
|
START_LIBEBML_NAMESPACE
|
|
|
|
EbmlMaster::EbmlMaster(const EbmlSemanticContext & aContext, bool bSizeIsknown)
|
|
:EbmlElement(0), Context(aContext), bChecksumUsed(bChecksumUsedByDefault)
|
|
{
|
|
SetSizeIsFinite(bSizeIsknown);
|
|
SetValueIsSet();
|
|
ProcessMandatory();
|
|
}
|
|
|
|
EbmlMaster::EbmlMaster(const EbmlMaster & ElementToClone)
|
|
:EbmlElement(ElementToClone)
|
|
,ElementList(ElementToClone.ListSize())
|
|
,Context(ElementToClone.Context)
|
|
,bChecksumUsed(ElementToClone.bChecksumUsed)
|
|
,Checksum(ElementToClone.Checksum)
|
|
{
|
|
// add a clone of the list
|
|
std::vector<EbmlElement *>::const_iterator Itr = ElementToClone.ElementList.begin();
|
|
std::vector<EbmlElement *>::iterator myItr = ElementList.begin();
|
|
while (Itr != ElementToClone.ElementList.end())
|
|
{
|
|
*myItr = (*Itr)->Clone();
|
|
Itr++; myItr++;
|
|
}
|
|
|
|
}
|
|
|
|
EbmlMaster::~EbmlMaster()
|
|
{
|
|
assert(!IsLocked()); // you're trying to delete a locked element !!!
|
|
|
|
size_t Index;
|
|
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
if (!(*ElementList[Index]).IsLocked()) {
|
|
delete ElementList[Index];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\todo handle exception on errors
|
|
\todo write all the Mandatory elements in the Context, otherwise assert
|
|
*/
|
|
uint32 EbmlMaster::RenderData(IOCallback & output, bool bForceRender, bool bKeepIntact)
|
|
{
|
|
uint32 Result = 0;
|
|
size_t Index;
|
|
|
|
if (!bForceRender) {
|
|
assert(CheckMandatory());
|
|
}
|
|
|
|
if (!bChecksumUsed) { // old school
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
if (!bKeepIntact && (ElementList[Index])->IsDefaultValue())
|
|
continue;
|
|
Result += (ElementList[Index])->Render(output, bKeepIntact, false ,bForceRender);
|
|
}
|
|
} else { // new school
|
|
MemIOCallback TmpBuf(GetSize() - 6);
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
if (!bKeepIntact && (ElementList[Index])->IsDefaultValue())
|
|
continue;
|
|
(ElementList[Index])->Render(TmpBuf, bKeepIntact, false ,bForceRender);
|
|
}
|
|
Checksum.FillCRC32(TmpBuf.GetDataBuffer(), TmpBuf.GetDataBufferSize());
|
|
Result += Checksum.Render(output, true, false ,bForceRender);
|
|
output.writeFully(TmpBuf.GetDataBuffer(), TmpBuf.GetDataBufferSize());
|
|
Result += TmpBuf.GetDataBufferSize();
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
/*!
|
|
\todo We might be able to forbid elements that don't exist in the context
|
|
*/
|
|
bool EbmlMaster::PushElement(EbmlElement & element)
|
|
{
|
|
ElementList.push_back(&element);
|
|
return true;
|
|
}
|
|
|
|
uint64 EbmlMaster::UpdateSize(bool bKeepIntact, bool bForceRender)
|
|
{
|
|
SetSize_(0);
|
|
|
|
if (!IsFiniteSize())
|
|
return (0-1);
|
|
|
|
if (!bForceRender) {
|
|
assert(CheckMandatory());
|
|
}
|
|
|
|
size_t Index;
|
|
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
if (!bKeepIntact && (ElementList[Index])->IsDefaultValue())
|
|
continue;
|
|
(ElementList[Index])->UpdateSize(bKeepIntact, bForceRender);
|
|
uint64 SizeToAdd = (ElementList[Index])->ElementSize(bKeepIntact);
|
|
#if defined(_DEBUG) || defined(DEBUG)
|
|
if (SizeToAdd == (0-1))
|
|
return (0-1);
|
|
#endif // DEBUG
|
|
SetSize_(GetSize() + SizeToAdd);
|
|
}
|
|
if (bChecksumUsed) {
|
|
SetSize_(GetSize() + Checksum.ElementSize());
|
|
}
|
|
|
|
return GetSize();
|
|
}
|
|
|
|
uint32 EbmlMaster::WriteHead(IOCallback & output, int nSizeLength, bool bKeepIntact)
|
|
{
|
|
SetSizeLength(nSizeLength);
|
|
return RenderHead(output, false, bKeepIntact);
|
|
}
|
|
|
|
/*!
|
|
\todo this code is very suspicious !
|
|
*/
|
|
uint64 EbmlMaster::ReadData(IOCallback & input, ScopeMode ReadFully)
|
|
{
|
|
input.setFilePointer(GetSize(), seek_current);
|
|
return GetSize();
|
|
}
|
|
|
|
/*!
|
|
\note Hopefully no global element is mandatory
|
|
\todo should be called for ALL EbmlMaster element on construction
|
|
*/
|
|
bool EbmlMaster::ProcessMandatory()
|
|
{
|
|
if (Context.Size == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
assert(Context.MyTable != NULL);
|
|
|
|
unsigned int EltIdx;
|
|
for (EltIdx = 0; EltIdx < Context.Size; EltIdx++) {
|
|
if (Context.MyTable[EltIdx].Mandatory && Context.MyTable[EltIdx].Unique) {
|
|
assert(Context.MyTable[EltIdx].GetCallbacks.Create != NULL);
|
|
PushElement(Context.MyTable[EltIdx].GetCallbacks.Create());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EbmlMaster::CheckMandatory() const
|
|
{
|
|
assert(Context.MyTable != NULL);
|
|
|
|
unsigned int EltIdx;
|
|
for (EltIdx = 0; EltIdx < Context.Size; EltIdx++) {
|
|
if (Context.MyTable[EltIdx].Mandatory) {
|
|
if (FindElt(Context.MyTable[EltIdx].GetCallbacks) == NULL) {
|
|
#if defined(_DEBUG) || defined(DEBUG)
|
|
// you are missing this Mandatory element
|
|
// const char * MissingName = Context.MyTable[EltIdx].GetCallbacks.DebugName;
|
|
#endif // DEBUG
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> EbmlMaster::FindAllMissingElements()
|
|
{
|
|
assert(Context.MyTable != NULL);
|
|
|
|
std::vector<std::string> missingElements;
|
|
|
|
for (size_t ChildElementNo = 0; ChildElementNo < ElementList.size(); ChildElementNo++) {
|
|
EbmlElement *childElement = ElementList[ChildElementNo];
|
|
if (!childElement->ValueIsSet()) {
|
|
std::string missingValue;
|
|
missingValue = "The Child Element \"";
|
|
missingValue.append(EBML_NAME(childElement));
|
|
missingValue.append("\" of EbmlMaster \"");
|
|
missingValue.append(EBML_NAME(this));
|
|
missingValue.append("\", does not have a value set.");
|
|
missingElements.push_back(missingValue);
|
|
}
|
|
|
|
if (childElement->IsMaster()) {
|
|
EbmlMaster *childMaster = (EbmlMaster *)childElement;
|
|
|
|
std::vector<std::string> childMissingElements = childMaster->FindAllMissingElements();
|
|
for (size_t s = 0; s < childMissingElements.size(); s++)
|
|
missingElements.push_back(childMissingElements[s]);
|
|
}
|
|
}
|
|
unsigned int EltIdx;
|
|
for (EltIdx = 0; EltIdx < Context.Size; EltIdx++) {
|
|
if (Context.MyTable[EltIdx].Mandatory) {
|
|
if (FindElt(Context.MyTable[EltIdx].GetCallbacks) == NULL) {
|
|
std::string missingElement;
|
|
missingElement = "Missing element \"";
|
|
missingElement.append(Context.MyTable[EltIdx].GetCallbacks.DebugName);
|
|
missingElement.append("\" in EbmlMaster \"");
|
|
missingElement.append(Context.MasterElt->DebugName);
|
|
missingElement.append("\"");
|
|
missingElements.push_back(missingElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
return missingElements;
|
|
}
|
|
|
|
EbmlElement *EbmlMaster::FindElt(const EbmlCallbacks & Callbacks) const
|
|
{
|
|
size_t Index;
|
|
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
EbmlElement * tmp = ElementList[Index];
|
|
if (EbmlId(*tmp) == Callbacks.GlobalId)
|
|
return tmp;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
EbmlElement *EbmlMaster::FindFirstElt(const EbmlCallbacks & Callbacks, bool bCreateIfNull)
|
|
{
|
|
size_t Index;
|
|
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
if (EbmlId(*(ElementList[Index])) == Callbacks.GlobalId)
|
|
return ElementList[Index];
|
|
}
|
|
|
|
if (bCreateIfNull && Callbacks.Create != NULL) {
|
|
// add the element
|
|
EbmlElement *NewElt = &(Callbacks.Create());
|
|
if (NewElt == NULL)
|
|
return NULL;
|
|
|
|
if (!PushElement(*NewElt)) {
|
|
delete NewElt;
|
|
NewElt = NULL;
|
|
}
|
|
return NewElt;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
EbmlElement *EbmlMaster::FindFirstElt(const EbmlCallbacks & Callbacks) const
|
|
{
|
|
size_t Index;
|
|
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
if (EbmlId(*(ElementList[Index])) == Callbacks.GlobalId)
|
|
return ElementList[Index];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*!
|
|
\todo only return elements that are from the same type !
|
|
\todo the element might be the unique in the context !
|
|
*/
|
|
EbmlElement *EbmlMaster::FindNextElt(const EbmlElement & PastElt, bool bCreateIfNull)
|
|
{
|
|
size_t Index;
|
|
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
if ((ElementList[Index]) == &PastElt) {
|
|
// found past element, new one is :
|
|
Index++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (Index < ElementList.size()) {
|
|
if ((EbmlId)PastElt == (EbmlId)(*ElementList[Index]))
|
|
break;
|
|
Index++;
|
|
}
|
|
|
|
if (Index != ElementList.size())
|
|
return ElementList[Index];
|
|
|
|
if (bCreateIfNull && PastElt.Generic().Create != NULL) {
|
|
// add the element
|
|
EbmlElement *NewElt = &(PastElt.Generic().Create());
|
|
if (NewElt == NULL)
|
|
return NULL;
|
|
|
|
if (!PushElement(*NewElt)) {
|
|
delete NewElt;
|
|
NewElt = NULL;
|
|
}
|
|
return NewElt;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
EbmlElement *EbmlMaster::FindNextElt(const EbmlElement & PastElt) const
|
|
{
|
|
size_t Index;
|
|
|
|
for (Index = 0; Index < ElementList.size(); Index++) {
|
|
if ((ElementList[Index]) == &PastElt) {
|
|
// found past element, new one is :
|
|
Index++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (Index < ElementList.size()) {
|
|
if ((EbmlId)PastElt == (EbmlId)(*ElementList[Index]))
|
|
return ElementList[Index];
|
|
Index++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
EbmlElement *EbmlMaster::AddNewElt(const EbmlCallbacks & Callbacks)
|
|
{
|
|
// add the element
|
|
EbmlElement *NewElt = &(Callbacks.Create());
|
|
if (NewElt == NULL)
|
|
return NULL;
|
|
|
|
if (!PushElement(*NewElt)) {
|
|
delete NewElt;
|
|
NewElt = NULL;
|
|
}
|
|
return NewElt;
|
|
}
|
|
|
|
void EbmlMaster::Sort()
|
|
{
|
|
std::sort(ElementList.begin(), ElementList.end(), EbmlElement::CompareElements);
|
|
}
|
|
|
|
/*!
|
|
\brief Method to help reading a Master element and all subsequent children quickly
|
|
\todo add an option to discard even unknown elements
|
|
\todo handle when a mandatory element is not found
|
|
*/
|
|
void EbmlMaster::Read(EbmlStream & inDataStream, const EbmlSemanticContext & sContext, int & UpperEltFound, EbmlElement * & FoundElt, bool AllowDummyElt, ScopeMode ReadFully)
|
|
{
|
|
if (ReadFully != SCOPE_NO_DATA)
|
|
{
|
|
EbmlElement * ElementLevelA;
|
|
// remove all existing elements, including the mandatory ones...
|
|
size_t Index;
|
|
for (Index=0; Index<ElementList.size(); Index++) {
|
|
if (!(*ElementList[Index]).IsLocked()) {
|
|
delete ElementList[Index];
|
|
}
|
|
}
|
|
ElementList.clear();
|
|
uint64 MaxSizeToRead = GetSize();
|
|
|
|
// read blocks and discard the ones we don't care about
|
|
if (MaxSizeToRead > 0) {
|
|
inDataStream.I_O().setFilePointer(GetSizePosition() + GetSizeLength(), seek_beginning);
|
|
ElementLevelA = inDataStream.FindNextElement(sContext, UpperEltFound, MaxSizeToRead, AllowDummyElt);
|
|
while (ElementLevelA != NULL && MaxSizeToRead > 0 && UpperEltFound <= 0) {
|
|
MaxSizeToRead = GetEndPosition() - ElementLevelA->GetEndPosition(); // even if it's the default value
|
|
if (!AllowDummyElt && ElementLevelA->IsDummy()) {
|
|
ElementLevelA->SkipData(inDataStream, sContext);
|
|
delete ElementLevelA; // forget this unknown element
|
|
} else {
|
|
// more logical to do it afterward
|
|
ElementList.push_back(ElementLevelA);
|
|
|
|
ElementLevelA->Read(inDataStream, EBML_CONTEXT(ElementLevelA), UpperEltFound, FoundElt, AllowDummyElt, ReadFully);
|
|
|
|
// just in case
|
|
ElementLevelA->SkipData(inDataStream, EBML_CONTEXT(ElementLevelA));
|
|
}
|
|
|
|
if (UpperEltFound > 0) {
|
|
UpperEltFound--;
|
|
if (UpperEltFound > 0 || MaxSizeToRead <= 0)
|
|
goto processCrc;
|
|
ElementLevelA = FoundElt;
|
|
continue;
|
|
}
|
|
|
|
if (UpperEltFound < 0) {
|
|
UpperEltFound++;
|
|
if (UpperEltFound < 0)
|
|
goto processCrc;
|
|
}
|
|
|
|
if (MaxSizeToRead <= 0) {
|
|
goto processCrc;// this level is finished
|
|
}
|
|
|
|
ElementLevelA = inDataStream.FindNextElement(sContext, UpperEltFound, MaxSizeToRead, AllowDummyElt);
|
|
}
|
|
if (UpperEltFound > 0) {
|
|
FoundElt = ElementLevelA;
|
|
}
|
|
}
|
|
processCrc:
|
|
for (Index=0; Index<ElementList.size(); Index++) {
|
|
if ((EbmlId)(*ElementList[Index]) == EBML_ID(EbmlCrc32)) {
|
|
bChecksumUsed = true;
|
|
// remove the element
|
|
Checksum = *(static_cast<EbmlCrc32*>(ElementList[Index]));
|
|
delete ElementList[Index];
|
|
Remove(Index--);
|
|
}
|
|
}
|
|
SetValueIsSet();
|
|
}
|
|
}
|
|
|
|
void EbmlMaster::Remove(size_t Index)
|
|
{
|
|
if (Index < ElementList.size()) {
|
|
std::vector<EbmlElement *>::iterator Itr = ElementList.begin();
|
|
while (Index-- > 0) {
|
|
Itr++;
|
|
}
|
|
|
|
ElementList.erase(Itr);
|
|
}
|
|
}
|
|
|
|
bool EbmlMaster::VerifyChecksum() const
|
|
{
|
|
if (!bChecksumUsed)
|
|
return true;
|
|
|
|
EbmlCrc32 aChecksum;
|
|
/// \todo remove the Checksum if it's in the list
|
|
/// \todo find another way when not all default values are saved or (unknown from the reader !!!)
|
|
MemIOCallback TmpBuf(GetSize() - 6);
|
|
for (size_t Index = 0; Index < ElementList.size(); Index++) {
|
|
(ElementList[Index])->Render(TmpBuf, true, false, true);
|
|
}
|
|
aChecksum.FillCRC32(TmpBuf.GetDataBuffer(), TmpBuf.GetDataBufferSize());
|
|
return (aChecksum.GetCrc32() == Checksum.GetCrc32());
|
|
}
|
|
|
|
bool EbmlMaster::InsertElement(EbmlElement & element, size_t position)
|
|
{
|
|
std::vector<EbmlElement *>::iterator Itr = ElementList.begin();
|
|
while (Itr != ElementList.end() && position--)
|
|
{
|
|
Itr++;
|
|
}
|
|
if ((Itr == ElementList.end()) && position)
|
|
return false;
|
|
|
|
ElementList.insert(Itr, &element);
|
|
return true;
|
|
}
|
|
|
|
bool EbmlMaster::InsertElement(EbmlElement & element, const EbmlElement & before)
|
|
{
|
|
std::vector<EbmlElement *>::iterator Itr = ElementList.begin();
|
|
while (Itr != ElementList.end() && *Itr != &before)
|
|
{
|
|
Itr++;
|
|
}
|
|
if (Itr == ElementList.end())
|
|
return false;
|
|
|
|
ElementList.insert(Itr, &element);
|
|
return true;
|
|
}
|
|
|
|
|
|
END_LIBEBML_NAMESPACE
|