1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00
vdr/libsi/si.c

730 lines
23 KiB
C
Raw Normal View History

2003-11-22 17:56:45 +01:00
/***************************************************************************
* Copyright (c) 2003 by Marcel Wiesweg *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* $Id: si.c 2.1 2009/12/05 16:20:12 kls Exp $
* *
2003-11-22 17:56:45 +01:00
***************************************************************************/
#include "si.h"
#include <errno.h>
#include <iconv.h>
#include <malloc.h>
#include <stdlib.h> // for broadcaster stupidity workaround
#include <string.h>
2003-11-22 17:56:45 +01:00
#include "descriptor.h"
namespace SI {
Object::Object() {
}
Object::Object(CharArray &d) : data(d) {
}
void Object::setData(const unsigned char*d, int size, bool doCopy) {
2003-11-22 17:56:45 +01:00
data.assign(d, size, doCopy);
}
void Object::setData(CharArray &d) {
data=d;
}
bool Object::checkSize(int offset) {
return data.checkSize(offset);
}
2003-11-22 17:56:45 +01:00
Section::Section(const unsigned char *data, bool doCopy) {
setData(data, getLength(data), doCopy);
}
TableId Section::getTableId() const {
return getTableId(data.getData());
}
int Section::getLength() {
return getLength(data.getData());
}
TableId Section::getTableId(const unsigned char *d) {
return (TableId)((const SectionHeader *)d)->table_id;
2003-11-22 17:56:45 +01:00
}
int Section::getLength(const unsigned char *d) {
return HILO(((const SectionHeader *)d)->section_length)+sizeof(SectionHeader);
2003-11-22 17:56:45 +01:00
}
bool CRCSection::isCRCValid() {
2003-11-22 17:56:45 +01:00
return CRC32::isValid((const char *)data.getData(), getLength()/*, data.FourBytes(getLength()-4)*/);
}
bool CRCSection::CheckCRCAndParse() {
if (!isCRCValid())
2003-11-22 17:56:45 +01:00
return false;
CheckParse();
return isValid();
2003-11-22 17:56:45 +01:00
}
int NumberedSection::getTableIdExtension() const {
return getTableIdExtension(data.getData());
}
int NumberedSection::getTableIdExtension(const unsigned char *d) {
return HILO(((const ExtendedSectionHeader *)d)->table_id_extension);
}
2003-11-22 17:56:45 +01:00
bool NumberedSection::getCurrentNextIndicator() const {
return data.getData<ExtendedSectionHeader>()->current_next_indicator;
}
int NumberedSection::getVersionNumber() const {
return data.getData<ExtendedSectionHeader>()->version_number;
}
int NumberedSection::getSectionNumber() const {
return data.getData<ExtendedSectionHeader>()->section_number;
}
int NumberedSection::getLastSectionNumber() const {
return data.getData<ExtendedSectionHeader>()->last_section_number;
}
int Descriptor::getLength() {
return getLength(data.getData());
}
DescriptorTag Descriptor::getDescriptorTag() const {
return getDescriptorTag(data.getData());
}
int Descriptor::getLength(const unsigned char *d) {
return ((const DescriptorHeader*)d)->descriptor_length+sizeof(DescriptorHeader);
}
DescriptorTag Descriptor::getDescriptorTag(const unsigned char *d) {
return (DescriptorTag)((const DescriptorHeader*)d)->descriptor_tag;
}
Descriptor *DescriptorLoop::getNext(Iterator &it) {
if (isValid() && it.i<getLength()) {
return createDescriptor(it.i, true);
2003-11-22 17:56:45 +01:00
}
return 0;
}
Descriptor *DescriptorLoop::getNext(Iterator &it, DescriptorTag tag, bool returnUnimplemetedDescriptor) {
Descriptor *d=0;
int len;
if (isValid() && it.i<(len=getLength())) {
2003-11-22 17:56:45 +01:00
const unsigned char *p=data.getData(it.i);
const unsigned char *end=p+len-it.i;
2003-11-22 17:56:45 +01:00
while (p < end) {
if (Descriptor::getDescriptorTag(p) == tag) {
d=createDescriptor(it.i, returnUnimplemetedDescriptor);
if (d)
break;
2003-11-22 17:56:45 +01:00
}
it.i+=Descriptor::getLength(p);
p+=Descriptor::getLength(p);
}
}
return d;
}
Descriptor *DescriptorLoop::getNext(Iterator &it, DescriptorTag *tags, int arrayLength, bool returnUnimplementedDescriptor) {
2003-11-22 17:56:45 +01:00
Descriptor *d=0;
int len;
if (isValid() && it.i<(len=getLength())) {
2003-11-22 17:56:45 +01:00
const unsigned char *p=data.getData(it.i);
const unsigned char *end=p+len-it.i;
2003-11-22 17:56:45 +01:00
while (p < end) {
for (int u=0; u<arrayLength;u++)
if (Descriptor::getDescriptorTag(p) == tags[u]) {
d=createDescriptor(it.i, returnUnimplementedDescriptor);
2003-11-22 17:56:45 +01:00
break;
}
if (d)
break; //length is added to it.i by createDescriptor, break here
2003-11-22 17:56:45 +01:00
it.i+=Descriptor::getLength(p);
p+=Descriptor::getLength(p);
}
}
return d;
}
Descriptor *DescriptorLoop::createDescriptor(int &i, bool returnUnimplemetedDescriptor) {
if (!checkSize(Descriptor::getLength(data.getData(i))))
return 0;
Descriptor *d=Descriptor::getDescriptor(data+i, domain, returnUnimplemetedDescriptor);
if (!d)
return 0;
2003-11-22 17:56:45 +01:00
i+=d->getLength();
d->CheckParse();
return d;
}
int DescriptorLoop::getNumberOfDescriptors() {
const unsigned char *p=data.getData();
const unsigned char *end=p+getLength();
int count=0;
while (p < end) {
count++;
p+=Descriptor::getLength(p);
}
return count;
}
2003-11-22 17:56:45 +01:00
DescriptorGroup::DescriptorGroup(bool del) {
array=0;
length=0;
deleteOnDesctruction=del;
}
DescriptorGroup::~DescriptorGroup() {
if (deleteOnDesctruction)
Delete();
delete[] array;
}
void DescriptorGroup::Delete() {
for (int i=0;i<length;i++)
if (array[i]!=0) {
delete array[i];
array[i]=0;
}
}
void DescriptorGroup::Add(GroupDescriptor *d) {
if (!array) {
length=d->getLastDescriptorNumber()+1;
array=new GroupDescriptor*[length]; //numbering is zero-based
for (int i=0;i<length;i++)
array[i]=0;
} else if (length != d->getLastDescriptorNumber()+1)
return; //avoid crash in case of misuse
array[d->getDescriptorNumber()]=d;
}
bool DescriptorGroup::isComplete() {
for (int i=0;i<length;i++)
if (array[i]==0)
return false;
return true;
}
char *String::getText() {
int len=getLength();
if (len < 0 || len > 4095)
return strdup("text error"); // caller will delete it!
char *data=new char(len+1); // FIXME If this function is ever actually used, this buffer might
// need to be bigger in order to hold the string as UTF-8.
// Maybe decodeText should dynamically handle this? kls 2007-06-10
decodeText(data, len+1);
2003-11-22 17:56:45 +01:00
return data;
}
char *String::getText(char *buffer, int size) {
int len=getLength();
if (len < 0 || len >= size) {
strncpy(buffer, "text error", size);
buffer[size-1] = 0;
2003-11-22 17:56:45 +01:00
return buffer;
}
decodeText(buffer, size);
2003-11-22 17:56:45 +01:00
return buffer;
}
char *String::getText(char *buffer, char *shortVersion, int sizeBuffer, int sizeShortVersion) {
int len=getLength();
if (len < 0 || len >= sizeBuffer) {
strncpy(buffer, "text error", sizeBuffer);
buffer[sizeBuffer-1] = 0;
*shortVersion = 0;
return buffer;
}
decodeText(buffer, shortVersion, sizeBuffer, sizeShortVersion);
return buffer;
}
static const char *CharacterTables1[] = {
NULL, // 0x00
"ISO-8859-5", // 0x01
"ISO-8859-6", // 0x02
"ISO-8859-7", // 0x03
"ISO-8859-8", // 0x04
"ISO-8859-9", // 0x05
"ISO-8859-10", // 0x06
"ISO-8859-11", // 0x07
"ISO-8859-12", // 0x08
"ISO-8859-13", // 0x09
"ISO-8859-14", // 0x0A
"ISO-8859-15", // 0x0B
NULL, // 0x0C
NULL, // 0x0D
NULL, // 0x0E
NULL, // 0x0F
NULL, // 0x10
"UTF-16", // 0x11
"EUC-KR", // 0x12
"GB2312", // 0x13
"GBK", // 0x14
"UTF-8", // 0x15
NULL, // 0x16
NULL, // 0x17
NULL, // 0x18
NULL, // 0x19
NULL, // 0x1A
NULL, // 0x1B
NULL, // 0x1C
NULL, // 0x1D
NULL, // 0x1E
NULL, // 0x1F
};
#define SingleByteLimit 0x0B
static const char *CharacterTables2[] = {
2007-07-21 13:49:48 +02:00
NULL, // 0x00
"ISO-8859-1", // 0x01
"ISO-8859-2", // 0x02
"ISO-8859-3", // 0x03
"ISO-8859-4", // 0x04
"ISO-8859-5", // 0x05
"ISO-8859-6", // 0x06
"ISO-8859-7", // 0x07
"ISO-8859-8", // 0x08
"ISO-8859-9", // 0x09
"ISO-8859-10", // 0x0A
"ISO-8859-11", // 0x0B
2007-07-21 13:49:48 +02:00
NULL, // 0x0C
"ISO-8859-13", // 0x0D
"ISO-8859-14", // 0x0E
"ISO-8859-15", // 0x0F
};
#define NumEntries(Table) (sizeof(Table) / sizeof(char *))
static const char *SystemCharacterTable = NULL;
bool SystemCharacterTableIsSingleByte = true;
bool SetSystemCharacterTable(const char *CharacterTable) {
if (CharacterTable) {
for (unsigned int i = 0; i < NumEntries(CharacterTables1); i++) {
if (CharacterTables1[i] && strcasecmp(CharacterTable, CharacterTables1[i]) == 0) {
SystemCharacterTable = CharacterTables1[i];
SystemCharacterTableIsSingleByte = i <= SingleByteLimit;
return true;
}
}
for (unsigned int i = 0; i < NumEntries(CharacterTables2); i++) {
if (CharacterTables2[i] && strcasecmp(CharacterTable, CharacterTables2[i]) == 0) {
SystemCharacterTable = CharacterTables2[i];
SystemCharacterTableIsSingleByte = true;
return true;
}
}
} else {
SystemCharacterTable = NULL;
SystemCharacterTableIsSingleByte = true;
return true;
}
return false;
}
// Determines the character table used in the given buffer and returns
// a string indicating that table. If no table can be determined, the
// default ISO6937 is returned. If a table can be determined, the buffer
// and length are adjusted accordingly.
static const char *getCharacterTable(const unsigned char *&buffer, int &length, bool *isSingleByte = NULL) {
const char *cs = "ISO6937";
// Workaround for broadcaster stupidity: according to
// "ETSI EN 300 468" the default character set is ISO6937. But unfortunately some
// broadcasters actually use ISO-8859-9, but fail to correctly announce that.
static const char *CharsetOverride = getenv("VDR_CHARSET_OVERRIDE");
if (CharsetOverride)
cs = CharsetOverride;
if (isSingleByte)
*isSingleByte = false;
if (length <= 0)
return cs;
unsigned int tag = buffer[0];
if (tag >= 0x20)
return cs;
if (tag == 0x10) {
if (length >= 3) {
tag = (buffer[1] << 8) | buffer[2];
if (tag < NumEntries(CharacterTables2) && CharacterTables2[tag]) {
buffer += 3;
length -= 3;
if (isSingleByte)
*isSingleByte = true;
return CharacterTables2[tag];
}
}
} else if (tag < NumEntries(CharacterTables1) && CharacterTables1[tag]) {
buffer += 1;
length -= 1;
if (isSingleByte)
*isSingleByte = tag <= SingleByteLimit;
return CharacterTables1[tag];
}
return cs;
}
static bool convertCharacterTable(const char *from, size_t fromLength, char *to, size_t toLength, const char *fromCode)
{
if (SystemCharacterTable) {
iconv_t cd = iconv_open(SystemCharacterTable, fromCode);
if (cd != (iconv_t)-1) {
char *fromPtr = (char *)from;
while (fromLength > 0 && toLength > 1) {
if (iconv(cd, &fromPtr, &fromLength, &to, &toLength) == size_t(-1)) {
if (errno == EILSEQ) {
// A character can't be converted, so mark it with '?' and proceed:
fromPtr++;
fromLength--;
*to++ = '?';
toLength--;
}
else
break;
}
}
*to = 0;
iconv_close(cd);
return true;
}
}
return false;
}
// originally from libdtv, Copyright Rolf Hakenes <hakenes@hippomi.de>
void String::decodeText(char *buffer, int size) {
2003-11-22 17:56:45 +01:00
const unsigned char *from=data.getData(0);
char *to=buffer;
int len=getLength();
if (len <= 0) {
*to = '\0';
return;
}
bool singleByte;
const char *cs = getCharacterTable(from, len, &singleByte);
// FIXME Need to make this UTF-8 aware (different control codes).
// However, there's yet to be found a broadcaster that actually
// uses UTF-8 for the SI data... (kls 2007-06-10)
for (int i = 0; i < len; i++) {
2003-11-22 17:56:45 +01:00
if (*from == 0)
break;
if ( ((' ' <= *from) && (*from <= '~'))
2003-11-22 17:56:45 +01:00
|| (*from == '\n')
|| (0xA0 <= *from)
2003-11-22 17:56:45 +01:00
)
*to++ = *from;
else if (*from == 0x8A)
*to++ = '\n';
from++;
if (to - buffer >= size - 1)
break;
}
*to = '\0';
if (!singleByte || !SystemCharacterTableIsSingleByte) {
char convBuffer[size];
if (convertCharacterTable(buffer, strlen(buffer), convBuffer, sizeof(convBuffer), cs))
strncpy(buffer, convBuffer, strlen(convBuffer) + 1);
}
}
void String::decodeText(char *buffer, char *shortVersion, int sizeBuffer, int sizeShortVersion) {
const unsigned char *from=data.getData(0);
char *to=buffer;
char *toShort=shortVersion;
int IsShortName=0;
int len=getLength();
if (len <= 0) {
*to = '\0';
*toShort = '\0';
return;
}
bool singleByte;
const char *cs = getCharacterTable(from, len, &singleByte);
// FIXME Need to make this UTF-8 aware (different control codes).
// However, there's yet to be found a broadcaster that actually
// uses UTF-8 for the SI data... (kls 2007-06-10)
for (int i = 0; i < len; i++) {
if ( ((' ' <= *from) && (*from <= '~'))
|| (*from == '\n')
|| (0xA0 <= *from)
)
{
*to++ = *from;
if (IsShortName)
*toShort++ = *from;
}
else if (*from == 0x8A)
*to++ = '\n';
else if (*from == 0x86)
IsShortName++;
else if (*from == 0x87)
IsShortName--;
else if (*from == 0)
break;
2003-11-22 17:56:45 +01:00
from++;
if (to - buffer >= sizeBuffer - 1 || toShort - shortVersion >= sizeShortVersion - 1)
break;
2003-11-22 17:56:45 +01:00
}
*to = '\0';
*toShort = '\0';
if (!singleByte || !SystemCharacterTableIsSingleByte) {
char convBuffer[sizeBuffer];
if (convertCharacterTable(buffer, strlen(buffer), convBuffer, sizeof(convBuffer), cs))
strncpy(buffer, convBuffer, strlen(convBuffer) + 1);
char convShortVersion[sizeShortVersion];
if (convertCharacterTable(shortVersion, strlen(shortVersion), convShortVersion, sizeof(convShortVersion), cs))
strncpy(shortVersion, convShortVersion, strlen(convShortVersion) + 1);
}
2003-11-22 17:56:45 +01:00
}
Descriptor *Descriptor::getDescriptor(CharArray da, DescriptorTagDomain domain, bool returnUnimplemetedDescriptor) {
2003-11-22 17:56:45 +01:00
Descriptor *d=0;
switch (domain) {
case SI:
switch ((DescriptorTag)da.getData<DescriptorHeader>()->descriptor_tag) {
case CaDescriptorTag:
d=new CaDescriptor();
break;
case CarouselIdentifierDescriptorTag:
d=new CarouselIdentifierDescriptor();
break;
case NetworkNameDescriptorTag:
d=new NetworkNameDescriptor();
break;
case ServiceListDescriptorTag:
d=new ServiceListDescriptor();
break;
case SatelliteDeliverySystemDescriptorTag:
d=new SatelliteDeliverySystemDescriptor();
break;
case CableDeliverySystemDescriptorTag:
d=new CableDeliverySystemDescriptor();
break;
case TerrestrialDeliverySystemDescriptorTag:
d=new TerrestrialDeliverySystemDescriptor();
break;
case BouquetNameDescriptorTag:
d=new BouquetNameDescriptor();
break;
case ServiceDescriptorTag:
d=new ServiceDescriptor();
break;
case NVODReferenceDescriptorTag:
d=new NVODReferenceDescriptor();
break;
case TimeShiftedServiceDescriptorTag:
d=new TimeShiftedServiceDescriptor();
break;
case ComponentDescriptorTag:
d=new ComponentDescriptor();
break;
case StreamIdentifierDescriptorTag:
d=new StreamIdentifierDescriptor();
break;
case SubtitlingDescriptorTag:
d=new SubtitlingDescriptor();
break;
case MultilingualNetworkNameDescriptorTag:
d=new MultilingualNetworkNameDescriptor();
break;
case MultilingualBouquetNameDescriptorTag:
d=new MultilingualBouquetNameDescriptor();
break;
case MultilingualServiceNameDescriptorTag:
d=new MultilingualServiceNameDescriptor();
break;
case MultilingualComponentDescriptorTag:
d=new MultilingualComponentDescriptor();
break;
case PrivateDataSpecifierDescriptorTag:
d=new PrivateDataSpecifierDescriptor();
break;
2003-11-22 17:56:45 +01:00
case ServiceMoveDescriptorTag:
d=new ServiceMoveDescriptor();
break;
case FrequencyListDescriptorTag:
d=new FrequencyListDescriptor();
break;
case ServiceIdentifierDescriptorTag:
d=new ServiceIdentifierDescriptor();
break;
case CaIdentifierDescriptorTag:
d=new CaIdentifierDescriptor();
break;
2003-11-22 17:56:45 +01:00
case ShortEventDescriptorTag:
d=new ShortEventDescriptor();
break;
case ExtendedEventDescriptorTag:
d=new ExtendedEventDescriptor();
break;
case TimeShiftedEventDescriptorTag:
d=new TimeShiftedEventDescriptor();
break;
case ContentDescriptorTag:
d=new ContentDescriptor();
break;
case ParentalRatingDescriptorTag:
d=new ParentalRatingDescriptor();
break;
case TeletextDescriptorTag:
case VBITeletextDescriptorTag:
d=new TeletextDescriptor();
break;
2003-11-22 17:56:45 +01:00
case ApplicationSignallingDescriptorTag:
d=new ApplicationSignallingDescriptor();
break;
case LocalTimeOffsetDescriptorTag:
d=new LocalTimeOffsetDescriptor();
break;
case LinkageDescriptorTag:
d=new LinkageDescriptor();
break;
case ISO639LanguageDescriptorTag:
d=new ISO639LanguageDescriptor();
break;
case PDCDescriptorTag:
d=new PDCDescriptor();
break;
case AncillaryDataDescriptorTag:
d=new AncillaryDataDescriptor();
break;
2007-02-03 12:13:08 +01:00
case S2SatelliteDeliverySystemDescriptorTag:
d=new S2SatelliteDeliverySystemDescriptor();
break;
case ExtensionDescriptorTag:
d=new ExtensionDescriptor();
break;
2003-11-22 17:56:45 +01:00
//note that it is no problem to implement one
//of the unimplemented descriptors.
2003-11-22 17:56:45 +01:00
//defined in ISO-13818-1
case VideoStreamDescriptorTag:
case AudioStreamDescriptorTag:
case HierarchyDescriptorTag:
case RegistrationDescriptorTag:
case DataStreamAlignmentDescriptorTag:
case TargetBackgroundGridDescriptorTag:
case VideoWindowDescriptorTag:
case SystemClockDescriptorTag:
case MultiplexBufferUtilizationDescriptorTag:
case CopyrightDescriptorTag:
case MaximumBitrateDescriptorTag:
case PrivateDataIndicatorDescriptorTag:
case SmoothingBufferDescriptorTag:
case STDDescriptorTag:
case IBPDescriptorTag:
2003-11-22 17:56:45 +01:00
//defined in ETSI EN 300 468
case StuffingDescriptorTag:
case VBIDataDescriptorTag:
case CountryAvailabilityDescriptorTag:
case MocaicDescriptorTag:
case TelephoneDescriptorTag:
case CellListDescriptorTag:
case CellFrequencyLinkDescriptorTag:
case ServiceAvailabilityDescriptorTag:
case ShortSmoothingBufferDescriptorTag:
case PartialTransportStreamDescriptorTag:
case DataBroadcastDescriptorTag:
case DataBroadcastIdDescriptorTag:
2007-02-03 12:13:08 +01:00
case ScramblingDescriptorTag:
2003-11-22 17:56:45 +01:00
case AC3DescriptorTag:
case DSNGDescriptorTag:
case AnnouncementSupportDescriptorTag:
2003-11-22 17:56:45 +01:00
case AdaptationFieldDataDescriptorTag:
case TransportStreamDescriptorTag:
2007-02-03 12:13:08 +01:00
//defined in ETSI EN 300 468 v 1.7.1
case DefaultAuthorityDescriptorTag:
case RelatedContentDescriptorTag:
case TVAIdDescriptorTag:
case ContentIdentifierDescriptorTag:
case TimeSliceFecIdentifierDescriptorTag:
case ECMRepetitionRateDescriptorTag:
case EnhancedAC3DescriptorTag:
case DTSDescriptorTag:
case AACDescriptorTag:
2003-11-22 17:56:45 +01:00
default:
if (!returnUnimplemetedDescriptor)
return 0;
2003-11-22 17:56:45 +01:00
d=new UnimplementedDescriptor();
break;
}
break;
case MHP:
switch ((DescriptorTag)da.getData<DescriptorHeader>()->descriptor_tag) {
// They once again start with 0x00 (see page 234, MHP specification)
case MHP_ApplicationDescriptorTag:
d=new MHP_ApplicationDescriptor();
break;
case MHP_ApplicationNameDescriptorTag:
d=new MHP_ApplicationNameDescriptor();
break;
case MHP_TransportProtocolDescriptorTag:
d=new MHP_TransportProtocolDescriptor();
break;
case MHP_DVBJApplicationDescriptorTag:
d=new MHP_DVBJApplicationDescriptor();
break;
case MHP_DVBJApplicationLocationDescriptorTag:
d=new MHP_DVBJApplicationLocationDescriptor();
break;
// 0x05 - 0x0A is unimplemented this library
case MHP_ExternalApplicationAuthorisationDescriptorTag:
case MHP_IPv4RoutingDescriptorTag:
case MHP_IPv6RoutingDescriptorTag:
case MHP_DVBHTMLApplicationDescriptorTag:
case MHP_DVBHTMLApplicationLocationDescriptorTag:
case MHP_DVBHTMLApplicationBoundaryDescriptorTag:
case MHP_ApplicationIconsDescriptorTag:
case MHP_PrefetchDescriptorTag:
case MHP_DelegatedApplicationDescriptorTag:
case MHP_ApplicationStorageDescriptorTag:
default:
if (!returnUnimplemetedDescriptor)
return 0;
2003-11-22 17:56:45 +01:00
d=new UnimplementedDescriptor();
break;
}
break;
case PCIT:
switch ((DescriptorTag)da.getData<DescriptorHeader>()->descriptor_tag) {
case ContentDescriptorTag:
d=new ContentDescriptor();
break;
case ShortEventDescriptorTag:
d=new ShortEventDescriptor();
break;
case ExtendedEventDescriptorTag:
d=new ExtendedEventDescriptor();
break;
case PremiereContentTransmissionDescriptorTag:
d=new PremiereContentTransmissionDescriptor();
break;
default:
if (!returnUnimplemetedDescriptor)
return 0;
d=new UnimplementedDescriptor();
break;
}
break;
default: ; // unknown domain, nothing to do
2003-11-22 17:56:45 +01:00
}
d->setData(da);
return d;
}
} //end of namespace