/*
 *
 *  Copyright (C) 1996-2017, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmwlm
 *
 *  Author:  Thomas Wilkens
 *
 *  Purpose: (Partially) abstract class for connecting to an arbitrary data source.
 *
 */

// ----------------------------------------------------------------------------

#include "dcmtk/config/osconfig.h"   // specific configuration for operating system

#include "dcmtk/dcmnet/dicom.h"      // for DIC_NODENAME etc. used in "wltypdef.h"
#include "dcmtk/dcmwlm/wltypdef.h"   // for type definitions
#include "dcmtk/ofstd/oftypes.h"     // for OFBool
#include "dcmtk/dcmdata/dcdatset.h"  // for DcmDataset
#include "dcmtk/dcmdata/dcvrat.h"    // for DcmAttributTag
#include "dcmtk/dcmdata/dcvrlo.h"    // for DcmLongString
#include "dcmtk/dcmdata/dcvrae.h"
#include "dcmtk/dcmdata/dcvrda.h"
#include "dcmtk/dcmdata/dcvrcs.h"
#include "dcmtk/dcmdata/dcvrpn.h"
#include "dcmtk/dcmdata/dcvrtm.h"
#include "dcmtk/dcmdata/dcvrsh.h"
#include "dcmtk/dcmdata/dcvrui.h"
#include "dcmtk/dcmdata/dcdict.h"    // for global variable dcmDataDict
#include "dcmtk/dcmdata/dcdeftag.h"  // for DCM_OffendingElement, ...
#include "dcmtk/dcmdata/dcsequen.h"  // for DcmSequenceOfItems
#include "dcmtk/dcmdata/dcdicent.h"  // needed by MSVC5 with STL
#include "dcmtk/dcmwlm/wlds.h"

OFLogger DCM_dcmwlmLogger = OFLog::getLogger("dcmtk.dcmwlm");

// ----------------------------------------------------------------------------

WlmDataSource::WlmDataSource()
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : Constructor.
// Parameters   : none.
// Return Value : none.
  : failOnInvalidQuery( OFTrue ), calledApplicationEntityTitle(""),
    identifiers( NULL ), errorElements( NULL ), offendingElements( NULL ), errorComment( NULL ),
    foundUnsupportedOptionalKey( OFFalse ), readLockSetOnDataSource( OFFalse ),
    noSequenceExpansion( OFFalse ), returnedCharacterSet( RETURN_NO_CHARACTER_SET ), matchingDatasets(),
    specificCharacterSet( "" ), superiorSequenceArray( NULL ),
    numOfSuperiorSequences( 0 )
{
  // Make sure data dictionary is loaded.
  if( !dcmDataDict.isDictionaryLoaded() )
  {
    DCMWLM_WARN("No data dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE);
  }

  // Initialize member variables.
  identifiers = new DcmDataset();
  offendingElements = new DcmAttributeTag( DCM_OffendingElement);
  errorElements = new DcmAttributeTag( DCM_OffendingElement);
  errorComment = new DcmLongString( DCM_ErrorComment);
}

// ----------------------------------------------------------------------------

WlmDataSource::~WlmDataSource()
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : Destructor.
// Parameters   : none.
// Return Value : none.
{
  // free memory
  ClearDataset(identifiers);
  delete identifiers;
  delete offendingElements;
  delete errorElements;
  delete errorComment;
}

// ----------------------------------------------------------------------------

void WlmDataSource::SetCalledApplicationEntityTitle( const OFString& value )
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : Sets the member variable that specifies called application entity title.
// Parameters   : value - Value for calledApplicationEntityTitle.
// Return Value : none.
{
  calledApplicationEntityTitle = value;
}

// ----------------------------------------------------------------------------

void WlmDataSource::SetFailOnInvalidQuery( OFBool value )
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : Set member variable that determines if a call should fail on an invalid query.
// Parameters   : value - Value for failOnInvalidQuery.
// Return Value : none.
{
  failOnInvalidQuery = value;
}

// ----------------------------------------------------------------------------

void WlmDataSource::SetNoSequenceExpansion( OFBool value )
// Date         : May 8, 2002
// Author       : Thomas Wilkens
// Task         : Set member variable.
// Parameters   : value - Value for member variable.
// Return Value : none.
{
  noSequenceExpansion = value;
}

// ----------------------------------------------------------------------------

void WlmDataSource::SetReturnedCharacterSet( WlmReturnedCharacterSetType value )
// Date         : March 21, 2002
// Author       : Thomas Wilkens
// Task         : Set member variable.
// Parameters   : value - Value for member variable.
// Return Value : none.
{
  returnedCharacterSet = value;
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::CheckSearchMask( DcmDataset *searchMask )
// Date         : March 18, 2001
// Author       : Thomas Wilkens
// Task         : This function checks if the search mask has a correct format. It returns OFTrue if this
//                is the case, OFFalse if this is not the case.
// Parameters   : searchMask - [in] Contains the search mask.
// Return Value : OFTrue  - The search mask has a correct format.
//                OFFalse - The search mask does not have a correct format.
{
  // Initialize counter that counts invalid elements in the search mask. This
  // variable will later be used to determine the return value for this function.
  int invalidMatchingKeyAttributeCount = 0;

  // remember the number of data elements in the search mask
  unsigned long numOfElementsInSearchMask = searchMask->card();

  // remember potentially specified specific character set
  searchMask->findAndGetOFString( DCM_SpecificCharacterSet, specificCharacterSet );

  // dump some information if required
  DCMWLM_INFO("Checking the search mask");

  // this member variable indicates if we encountered an unsupported
  // optional key attribute in the search mask; initialize it with false.
  foundUnsupportedOptionalKey = OFFalse;

  // start a loop; go through all data elements in the search mask, for each element, do some checking
  unsigned long i = 0;
  while( i < numOfElementsInSearchMask )
  {
    // determine current element
    DcmElement *element = searchMask->getElement(i);

    // Depending on if the current element is a sequence or not, process this element.
    if( element->ident() != EVR_SQ )
      CheckNonSequenceElementInSearchMask( searchMask, invalidMatchingKeyAttributeCount, element );
    else
      CheckSequenceElementInSearchMask( searchMask, invalidMatchingKeyAttributeCount, element );

    // Depending on if the number of elements in the search mask has changed, the current element was
    // deleted in the above called function or not. In case the current element was deleted, we do not
    // need to increase counter i, we only need to update numOfElementsInSearchMask. If the current
    // element was not deleted, we need to increase i but don't have to update numOfElementsInSearchMask.
    unsigned long currentNumOfElementsInSearchMask = searchMask->card();
    if( currentNumOfElementsInSearchMask != numOfElementsInSearchMask )
      numOfElementsInSearchMask = currentNumOfElementsInSearchMask;
    else
      i++;
  }

  // if there was more than 1 error, override the error comment
  if( invalidMatchingKeyAttributeCount > 1 )
    errorComment->putString("Syntax error in 1 or more matching keys");

  // return OFTrue or OFFalse depending on the number of invalid matching attributes
  return( invalidMatchingKeyAttributeCount == 0 );
}

// ----------------------------------------------------------------------------

void WlmDataSource::CheckNonSequenceElementInSearchMask( DcmDataset *searchMask, int &invalidMatchingKeyAttributeCount, DcmElement *element, DcmSequenceOfItems *supSequenceElement )
// Date         : March 8, 2002
// Author       : Thomas Wilkens
// Task         : This function checks if a non-sequence element in the search mask has a correct format.
//                Note that if the current element is an unsupported element, the entire element will be re-
//                moved from the search mask, since unsupported elements shall not be returned to the caller.
// Parameters   : searchMask                       - [in] Pointer to the search mask.
//                invalidMatchingKeyAttributeCount - [inout] Counter that counts invalid elements in the search mask.
//                element                          - [in] Pointer to the currently processed element.
//                supSequenceElement               - [in] Pointer to the superordinate sequence element of which
//                                                   the currently processed element is an attribute.
// Return Value : none.
{
  DcmElement *elem = NULL;

  // determine current element's tag
  DcmTag tag( element->getTag() );

  // determine if the current element is a supported matching key attribute
  if( IsSupportedMatchingKeyAttribute( element, supSequenceElement ) )
  {
    // if the current element is a supported matching key attribute, check if the current element meets
    // certain conditions (at the moment we only check if the element's data type and value go together)
    if( !CheckMatchingKey( element ) )
    {
      // if there is a problem with the current element, increase the corresponding counter and dump an error message.
      invalidMatchingKeyAttributeCount++;
      DCMWLM_WARN("Matching key attribute (" << tag.getTagName() << ") with invalid value encountered in the search mask");
    }
  }
  // if current element is not a supported matching key attribute, determine
  // if the current element is a supported return key attribute.
  else if( IsSupportedReturnKeyAttribute( element, supSequenceElement ) )
  {
    // we need to check if the current element (a supported return key attribute) does not contain a value.
    // According to the DICOM standard part 4, section K.2.2.1.2. a return key attribute which is NOT a
    // a matching key attribute must not contain a value. If one such attribute does contain a value,
    // i.e. if the current element's length does not equal 0, we want to dump a warning message.
    if( element->getLength() != 0 )
    {
      DCMWLM_INFO("  - Non-empty return key attribute (" << tag.getTagName() << ") encountered in the search mask." << OFendl
          << "    The specified value will be overridden.");
    }
  }
  // if current element is neither a supported matching key attribute nor a supported return key
  // attribute we've found an unsupported optional attribute; we want to delete this element from
  // the search mask, dump a warning and remember this attribute's tag in the list of error elements.
  else
  {
    // delete element from the search mask
    // in case there is a superordinate sequence element in whose first item this element occurs,
    // we need to delete this element from this particular item in the superordinate sequence element
    if( supSequenceElement != NULL )
    {
      elem = supSequenceElement->getItem(0)->remove( element );
      delete elem;
    }
    // in case there is NO superordinate sequence element in whose first item this element occurs,
    // we need to delete this element from the search mask itself.
    else
    {
      elem = searchMask->remove( element );
      delete elem;
    }

    // handle special case of "Specific Character Set"
    if( tag == DCM_SpecificCharacterSet )
    {
      DCMWLM_WARN("Attribute " << tag.getTagName() << " found in the search mask, value is neither checked nor used for matching");
    }
    else
    {
      // dump a warning
      DCMWLM_INFO("  - Unsupported (non-sequence) attribute (" << tag.getTagName() << ") encountered in the search mask." << OFendl
          << "    This attribute will not be existent in any result dataset.");

      // remember this attribute's tag in the list of error elements
      foundUnsupportedOptionalKey = OFTrue;
      PutErrorElements( tag );
    }
  }
}

// ----------------------------------------------------------------------------

void WlmDataSource::CheckSequenceElementInSearchMask( DcmDataset *searchMask, int &invalidMatchingKeyAttributeCount, DcmElement *element, DcmSequenceOfItems *supSequenceElement )
// Date         : March 8, 2002
// Author       : Thomas Wilkens
// Task         : This function checks if a sequence element in the search mask has a correct format.
//                Note that if the current element is an unsupported element, the entire element will be re-
//                moved from the search mask, since unsupported elements shall not be returned to the caller.
//                Moreover, in case the sequence element in the search mask is supported but empty, this
//                function will expand the sequence element by inserting all required attributes into that sequence.
// Parameters   : searchMask                       - [in] Pointer to the search mask.
//                invalidMatchingKeyAttributeCount - [inout] Counter that counts invalid elements in the search mask.
//                element                          - [in] Pointer to the currently processed element.
//                supSequenceElement               - [in] Pointer to the superordinate sequence element of which
//                                                   the currently processed element is an attribute.
// Return Value : none.
{
  // Be aware of the following remark: the DICOM standard specifies in part 4, section C.2.2.2.6
  // that if a search mask contains a sequence attribute which contains a single empty item, all
  // attributes from that particular sequence are in fact queried and shall be returned by the SCP.
  // This implementation accounts for this specification by expanding the search mask correspondingly.

  DcmElement *elem = NULL;

  // determine current element's tag
  DcmTag tag( element->getTag() );

  // remember that the current element is a sequence of items
  DcmSequenceOfItems *sequenceElement = OFstatic_cast(DcmSequenceOfItems *, element);

  // determine if the current sequence element is a supported matching or return key attribute
  if( IsSupportedMatchingKeyAttribute( element, supSequenceElement ) || IsSupportedReturnKeyAttribute( element, supSequenceElement ) )
  {
    // if the sequence attribute is supported, check if the length of the sequence equals 0, or if the sequence
    // contains exactly one empty item
    if( element->getLength() == 0 || ( sequenceElement->card() == 1 && sequenceElement->getItem(0)->card() == 0 ) )
    {
      // an empty sequence is not allowed in a C-FIND request
      if (element->getLength() == 0)
      {
        DCMWLM_WARN("Empty sequence (" << tag.getTagName() << ") encountered within the query, "
            << "treating as if an empty item within the sequence has been sent");
      }
      // if this is the case, we need to check the value of a variable
      // which pertains to a certain command line option
      if( noSequenceExpansion == OFFalse )
      {
        // if the user did not explicitly disable the expansion of empty sequences in C-FIND request
        // messages go ahead and expand this sequence according to the remark above
        ExpandEmptySequenceInSearchMask( element );
      }
    }
    else
    {
      // if this is not the case we want to check the cardinality of the sequence; note that there should
      // always be exactly one item in a sequence within the search mask.
      if( sequenceElement->card() != 1 )
      {
        // if there is not exactly one item in the sequence of items, we want to remember this
        // attribute in the list of offending elements, we want to set the error comment correspondingly,
        // we want to dump an error message and we want to increase the corresponding counter.
        PutOffendingElements(tag);
        errorComment->putString("More than 1 item in sequence.");
        DCMWLM_ERROR("More than one item in sequence (" << tag.getTagName() << ") within the query encountered, "
            << "discarding all items except for the first one");
        invalidMatchingKeyAttributeCount++;

        // also, we want to delete all items except the first one
        unsigned long numOfItems = sequenceElement->card();
        for( unsigned long i=1 ; i<numOfItems ; i++ )
        {
          delete sequenceElement->remove( i );
        }
      }

      // (now, even though we might have encountered an error, we go on scrutinizing the search mask)

      // get the first (and only) item in the sequence of items
      DcmItem *item = sequenceElement->getItem(0);

      // determine the cardinality of this item
      unsigned long numOfElementsInItem = item->card();
      unsigned long k = 0;

      // go through all elements of this item
      while( k < numOfElementsInItem )
      {
        // determine the current element
        DcmElement *elementseq = item->getElement(k);

        // Depending on if the current element is a sequence or not, process this element.
        if( elementseq->ident() != EVR_SQ )
          CheckNonSequenceElementInSearchMask( searchMask, invalidMatchingKeyAttributeCount, elementseq, sequenceElement );
        else
          CheckSequenceElementInSearchMask( searchMask, invalidMatchingKeyAttributeCount, elementseq, sequenceElement );

        // Depending on if the number of elements in the item has changed, the current element was
        // deleted in the above called function or not. In case the current element was deleted, we do not
        // need to increase counter k, we only need to update numOfElementsInItem. If the current
        // element was not deleted, we need to increase k but don't have to update numOfElementsInItem.
        unsigned long currentNumOfElementsInItem = item->card();
        if( currentNumOfElementsInItem != numOfElementsInItem )
          numOfElementsInItem = currentNumOfElementsInItem;
        else
          k++;
      }
    }
  }
  // if current element is neither a supported matching key attribute sequence nor a supported return key
  // attribute sequence we've found an unsupported sequence attribute; we want to delete this element from
  // the search mask, dump a warning message (only in verbose mode) remember this attribute in the list of
  // error elements.
  else
  {
    // delete element from the search mask
    // in case there is a superordinate sequence element in whose first item this element occurs,
    // we need to delete this element from this particular item in the superordinate sequence element
    if( supSequenceElement != NULL )
    {
      elem = supSequenceElement->getItem(0)->remove( element );
      delete elem;
    }
    // in case there is NO superordinate sequence element in whose first item this element occurs,
    // we need to delete this element from the search mask itself.
    else
    {
      elem = searchMask->remove( element );
      delete elem;
    }

    // dump a warning
    DCMWLM_INFO("  - Unsupported (sequence) attribute (" << tag.getTagName() << ") encountered in the search mask." << OFendl
        << "    This attribute will not be existent in any result dataset.");

    // remember this attribute's tag in the list of error elements
    foundUnsupportedOptionalKey = OFTrue;
    PutErrorElements( tag );
  }
}

// ----------------------------------------------------------------------------

void WlmDataSource::ExpandEmptySequenceInSearchMask( DcmElement *&element )
// Date         : March 8, 2002
// Author       : Thomas Wilkens
// Task         : According to the DICOM standard (part 4, section C.2.2.2.6), if a search mask
//                contains a sequence attribute which contains no item or a single empty item, all
//                attributes from that particular sequence are in fact queried and shall be returned
//                by the SCP. This implementation accounts for this specification by inserting a
//                corresponding single item with all required attributes into such empty sequences.
//                This function performs the insertion of the required item and attributes.
// Parameters   : element - [inout] Pointer to the currently processed element.
// Return Value : none.
{
  DcmItem *item = NULL;
  DcmElement *newElement = NULL;

  // remember that the current element is a sequence of items
  DcmSequenceOfItems *sequenceElement = OFstatic_cast(DcmSequenceOfItems *, element);

  // determine if the length of the sequence equals 0
  if( element->getLength() == 0 )
  {
    // if the length of the sequence attribute equals 0, we have to insert a single item
    item = new DcmItem();
    if( sequenceElement->insert( item ) != EC_Normal )
    {
      delete item;
      item = NULL;
    }
  }
  else
  {
    // if the length of the sequence attribute does not equal 0, we have to determine the
    // one and only item in the sequence
    item = sequenceElement->getItem(0);
  }

  // check if item does not equal NULL, only if this is the case, we can insert attributes
  // into that item. In general, this should always be the case.
  if( item != NULL )
  {
    // at this point, the current element contains exactly one item, which is empty, i.e. does not
    // contain any attributes. Now we have to insert the required attributes into this item.

    // depending on what kind of supported sequence attribute
    // was passed, we have to insert different attributes
    DcmTagKey key = element->getTag().getXTag();
    if( key == DCM_ScheduledProcedureStepSequence )
    {
      newElement = new DcmApplicationEntity( DcmTag( DCM_ScheduledStationAETitle ) );      if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmDate( DcmTag( DCM_ScheduledProcedureStepStartDate ) );           if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmTime( DcmTag( DCM_ScheduledProcedureStepStartTime ) );           if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmCodeString( DcmTag( DCM_Modality ) );                            if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmPersonName( DcmTag( DCM_ScheduledPerformingPhysicianName ) );    if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmLongString( DcmTag( DCM_ScheduledProcedureStepDescription ) );   if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmShortString( DcmTag( DCM_ScheduledStationName ) );               if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmShortString( DcmTag( DCM_ScheduledProcedureStepLocation ) );     if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmLongString( DcmTag( DCM_PreMedication ) );                       if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmShortString( DcmTag( DCM_ScheduledProcedureStepID ) );           if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmLongString( DcmTag( DCM_RequestedContrastAgent ) );              if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmLongString( DcmTag( DCM_CommentsOnTheScheduledProcedureStep ) ); if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmCodeString( DcmTag( DCM_ScheduledProcedureStepStatus ) );        if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmDate( DcmTag( DCM_ScheduledProcedureStepEndDate ) );             if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmTime( DcmTag( DCM_ScheduledProcedureStepEndTime ) );             if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmSequenceOfItems( DcmTag( DCM_ScheduledProtocolCodeSequence ) );
      if( item->insert( newElement ) != EC_Normal )
        delete newElement;
      else
      {
        DcmItem *item2 = new DcmItem();
        if( OFstatic_cast(DcmSequenceOfItems*, newElement)->insert( item2 ) != EC_Normal )
        {
          delete item2;
          item2 = NULL;
        }
        else
        {
          DcmElement *newElement2 = NULL;
          newElement2 = new DcmShortString( DcmTag( DCM_CodeValue ) );               if( item2->insert( newElement2 ) != EC_Normal ) delete newElement2;
          newElement2 = new DcmShortString( DcmTag( DCM_CodingSchemeVersion ) );     if( item2->insert( newElement2 ) != EC_Normal ) delete newElement2;
          newElement2 = new DcmShortString( DcmTag( DCM_CodingSchemeDesignator ) );  if( item2->insert( newElement2 ) != EC_Normal ) delete newElement2;
          newElement2 = new DcmLongString( DcmTag( DCM_CodeMeaning ) );              if( item2->insert( newElement2 ) != EC_Normal ) delete newElement2;
        }
      }
    }
    else if( key == DCM_ScheduledProtocolCodeSequence || key == DCM_RequestedProcedureCodeSequence )
    {
      newElement = new DcmShortString( DcmTag( DCM_CodeValue ) );               if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmShortString( DcmTag( DCM_CodingSchemeVersion ) );     if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmShortString( DcmTag( DCM_CodingSchemeDesignator ) );  if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmLongString( DcmTag( DCM_CodeMeaning ) );              if( item->insert( newElement ) != EC_Normal ) delete newElement;
    }
    else if( key == DCM_ReferencedStudySequence || key == DCM_ReferencedPatientSequence )
    {
      newElement = new DcmUniqueIdentifier( DcmTag( DCM_ReferencedSOPClassUID ) );       if( item->insert( newElement ) != EC_Normal ) delete newElement;
      newElement = new DcmUniqueIdentifier( DcmTag( DCM_ReferencedSOPInstanceUID ) );    if( item->insert( newElement ) != EC_Normal ) delete newElement;
    }
    else
    {
      // this code should never be executed; if it is, there is a logical error
      // in the source code and we want to dump a warning message
      DCMWLM_ERROR("WlmDataSource::ExpandEmptySequenceInSearchMask: Unsupported sequence attribute encountered");
    }
  }
  else
  {
    // this code should never be executed. if it is, we want to dump a warning message
    DCMWLM_ERROR("WlmDataSource::ExpandEmptySequenceInSearchMask: Unable to find item in sequence");
  }
}

// ----------------------------------------------------------------------------

void WlmDataSource::ClearDataset( DcmDataset *idents )
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : This function removes all elements from the given DcmDataset object.
// Parameters   : idents - [in] pointer to object which shall be cleared.
// Return Value : none.
{
  // If the given pointer is valid and refers to a non-empty structure
  if( idents && (idents->card()>0) )
  {
    // clear structure
    idents->clear();
  }
}

// ----------------------------------------------------------------------------

void WlmDataSource::PutOffendingElements( const DcmTagKey &tag )
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : This function inserts the tag of an offending element into the
//                corresponding member variable, unless this tag is already con-
//                tained in this variable.
// Parameters   : tag - [in] The tag that shall be inserted.
// Return Value : none.
{
  DcmTagKey errortag;

  // determine how many offending elements there have been so far
  unsigned long d = offendingElements->getVM();

  // if this is the first one, insert it at position 0
  if( d==0 )
  {
    offendingElements->putTagVal(tag, 0);
  }
  // if this is not the first one, insert it at the end
  // but only if it is not yet contained.
  else
  {
    OFBool tagFound = OFFalse;
    for( unsigned long j=0 ; j<d && !tagFound ; j++ )
    {
      offendingElements->getTagVal( errortag, j );
      if( errortag == tag )
        tagFound = OFTrue;
    }

    if( !tagFound )
      offendingElements->putTagVal( tag, d );
  }
}

// ----------------------------------------------------------------------------

void WlmDataSource::PutErrorElements( const DcmTagKey &tag )
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : This function inserts the tag of an error element into the
//                corresponding member variable, without checking if it is already
//                contained in this variable.
// Parameters   : tag - [in] The tag that shall be inserted.
// Return Value : none.
{
  // insert tag
  errorElements->putTagVal( tag, errorElements->getVM() );
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::CheckMatchingKey( const DcmElement *elem )
// Date         : March 18, 2002
// Author       : Thomas Wilkens
// Task         : This function checks if the passed matching key's value only uses characters
//                which are part of its data type's character repertoire. Note that at the moment
//                this application supports the following matching key attributes:
//                   DCM_ScheduledProcedureStepSequence                    (0040,0100)  SQ  R  1
//                    > DCM_ScheduledStationAETitle                        (0040,0001)  AE  R  1
//                    > DCM_ScheduledProcedureStepStartDate                (0040,0002)  DA  R  1
//                    > DCM_ScheduledProcedureStepStartTime                (0040,0003)  TM  R  1
//                    > DCM_Modality                                       (0008,0060)  CS  R  1
//                    > DCM_ScheduledPerformingPhysicianName               (0040,0006)  PN  R  2
//                   DCM_PatientName                                       (0010,0010)  PN  R  1
//                   DCM_ResponsiblePerson                                 (0010,2297)  PN  O  3
//                   DCM_ResponsiblePersonRole                             (0010,2298)  CS  O  3
//                   DCM_PatientID                                         (0010,0020)  LO  R  1
//                   DCM_AccessionNumber                                   (0008,0050)  SH  O  2
//                   DCM_RequestedProcedureID                              (0040,1001)  SH  O  1
//                   DCM_ReferringPhysicianName                            (0008,0090)  PN  O  2
//                   DCM_PatientSex                                        (0010,0040)  CS  O  2
//                   DCM_RequestingPhysician                               (0032,1032)  PN  O  2
//                   DCM_AdmissionID                                       (0038,0010)  LO  O  2
//                   DCM_RequestedProcedurePriority                        (0040,1003)  SH  O  2
//                   DCM_PatientBirthDate                                  (0010,0030)  DA  O  2
//                   DCM_IssuerOfPatientID                                 (0010,0021)  LO  O  3
//                   DCM_StudyDate                                         (0008,0020)  DA  O  3
//                   DCM_StudyTime                                         (0008,0030)  TM  O  3
//                As a result, the following data types have to be supported in this function:
//                AE, DA, TM, CS, PN, LO and SH. For the correct specification of these datatypes
//                2003 DICOM standard, part 5, section 6.2, table 6.2-1.
// Parameters   : elem - [in] Element which shall be checked.
// Return Value : OFTrue  - The given element's value only uses characters which are part of
//                          the element's data type's character repertoire.
//                OFFalse - The given element's value does not only use characters which are part of
//                          the element's data type's character repertoire.
{
  OFBool ok = OFTrue;
  OFString val;

  switch( elem->ident() )
  {
    case EVR_DA:
      // get string value
      ok = GetStringValue( elem, val );
      // if there is a value and if the value is not a date or a date range, return invalid value
      if( ok && !IsValidDateOrDateRange( val ) )
      {
        DcmTag tag( elem->getTag() );
        PutOffendingElements( tag );
        errorComment->putString("Invalid value for an attribute of datatype DA");
        ok = OFFalse;
      }
      else
        ok = OFTrue;
      break;

    case EVR_TM:
      // get string value
      ok = GetStringValue( elem, val );
      // if there is a value and if the value is not a time or a time range, return invalid value
      if( ok && !IsValidTimeOrTimeRange( val ) )
      {
        DcmTag tag( elem->getTag() );
        PutOffendingElements( tag );
        errorComment->putString("Invalid value for an attribute of datatype TM");
        ok = OFFalse;
      }
      else
        ok = OFTrue;
      break;

    case EVR_CS:
      // get string value
      ok = GetStringValue( elem, val );
      // check if value contains only valid characters
      if( ok && !ContainsOnlyValidCharacters( val.c_str(), "*?ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 _" ) )
      {
        DcmTag tag( elem->getTag() );
        PutOffendingElements( tag );
        errorComment->putString("Invalid Character Repertoire for datatype CS");
        ok = OFFalse;
      }
      else
        ok = OFTrue;
      break;

    case EVR_AE:
      // get string value
      ok = GetStringValue( elem, val );
      // check if value contains only valid characters
      if( ok && !ContainsOnlyValidCharacters( val.c_str(), " !\"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" ) )
      {
        DcmTag tag( elem->getTag() );
        PutOffendingElements( tag );
        errorComment->putString("Invalid Character Repertoire for datatype AE");
        ok = OFFalse;
      }
      else
        ok = OFTrue;
      break;

    case EVR_PN:
      // get string value
      ok = GetStringValue( elem, val );
      // check if value contains only valid characters (no ESC since only allowed for ISO 2022 encoding)
      if( ok && !ContainsOnlyValidCharacters( val.c_str(), "*? !\"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}" ) && specificCharacterSet == "" )
      {
        DcmTag tag( elem->getTag() );
        PutOffendingElements( tag );
        errorComment->putString("Invalid Character Repertoire for datatype PN");
        ok = OFFalse;
      }
      else
        ok = OFTrue;
      break;

    case EVR_LO:
      // get string value
      ok = GetStringValue( elem, val );
      // check if value contains only valid characters (no ESC since only allowed for ISO 2022 encoding)
      if( ok && !ContainsOnlyValidCharacters( val.c_str(), "*? !\"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}" ) && specificCharacterSet == "" )
      {
        DcmTag tag( elem->getTag() );
        PutOffendingElements( tag );
        errorComment->putString("Invalid Character Repertoire for datatype LO");
        ok = OFFalse;
      }
      else
        ok = OFTrue;
      break;

    case EVR_SH:
      // get string value
      ok = GetStringValue( elem, val );
      // check if value contains only valid characters (no ESC since only allowed for ISO 2022 encoding)
      if( ok && !ContainsOnlyValidCharacters( val.c_str(), "*? !\"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}" ) && specificCharacterSet == "" )
      {
        DcmTag tag( elem->getTag() );
        PutOffendingElements( tag );
        errorComment->putString("Invalid Character Repertoire for datatype SH");
        ok = OFFalse;
      }
      else
        ok = OFTrue;
      break;

    default:
      break;
  }

  return( ok );
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::IsValidDateOrDateRange( const OFString& value )
// Date         : March 19, 2002
// Author       : Thomas Wilkens
// Task         : This function checks if the given value is a valid date or date range.
// Parameters   : value - [in] The value which shall be checked.
// Return Value : OFTrue  - The given value is a valid date or date range.
//                OFFalse - The given value is not a valid date or date range.
{
  // create new string without leading or trailing blanks
  OFString dateRange = DeleteLeadingAndTrailingBlanks( value );

  if (dateRange.empty())
    return OFFalse;

  // check if only allowed characters occur in the string
  if( !ContainsOnlyValidCharacters( dateRange.c_str(), "0123456789.-" ) )
    return( OFFalse );

  // initialize return value
  OFBool isValidDateRange = OFFalse;

  // Determine if a hyphen occurs in the date range
  size_t hyphen = dateRange.find('-');
  if( hyphen != OFString_npos )
  {
    // determine if two date values are given or not
    if( dateRange[0] == '-' )
    {
      // if the hyphen occurs at the beginning, there is just one date value which has to be checked for validity
      isValidDateRange = IsValidDate( dateRange.substr(1) );
    }
    else if( dateRange[ dateRange.length() - 1 ] == '-' )
    {
      // if the hyphen occurs at the end, there is just one date value which has to be checked for validity
      isValidDateRange = IsValidDate( dateRange.substr(0, dateRange.length() -1 ));
    }
    else
    {
      // in this case the hyphen occurs somewhere in between beginning and end; hence there are two date values
      // which have to be checked for validity. Determine where the hyphen occurs exactly
      // check both dates for validity
      if( IsValidDate( dateRange.substr(0, dateRange.length()-hyphen-1 )) &&
          IsValidDate( dateRange.substr(        hyphen + 1             )) )
      {
        isValidDateRange = OFTrue;
      }
    }
  }
  else
  {
    // if there is no hyphen, there is just one date value which has to be checked for validity
    isValidDateRange = IsValidDate( dateRange );
  }

  // return result
  return( isValidDateRange );
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::IsValidDate( const OFString& value )
// Date         : March 19, 2002
// Author       : Thomas Wilkens
// Task         : This function checks if the given date value is valid.
//                According to the 2001 DICOM standard, part 5, Table 6.2-1, a date
//                value is either in format "yyyymmdd" or in format "yyyy.mm.dd",
//                so that e.g. "19840822" represents August 22, 1984.
// Parameters   : value - [in] The value which shall be checked.
// Return Value : OFTrue  - Date is valid.
//                OFFalse - Date is not valid.
{
  int year=0, month=0, day=0;

  // create new string without leading or trailing blanks
  OFString date = DeleteLeadingAndTrailingBlanks( value );
  // check parameter

  if( value.empty() )
    return( OFFalse );

  // check if only allowed characters occur in the string
  if( !ContainsOnlyValidCharacters( date.c_str(), "0123456789." ) )
    return( OFFalse );

  // initialize return value
  OFBool isValidDate = OFFalse;

  // check which of the two formats applies to the given string
  if( date.length() == 8 )
  {
    // scan given date string
    sscanf( date.c_str(), "%4d%2d%2d", &year, &month, &day );
    if( year > 0 && month >= 1 && month <= 12 && day >= 1 && day <= 31 )
      isValidDate = OFTrue;
  }
  else if( date.length() == 10 )
  {
    // scan given date string
    sscanf( date.c_str(), "%4d.%2d.%2d", &year, &month, &day );
    if( year > 0 && month >= 1 && month <= 12 && day >= 1 && day <= 31 )
      isValidDate = OFTrue;
  }

  // return result
  return( isValidDate );
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::IsValidTimeOrTimeRange( const OFString& value )
// Date         : March 19, 2002
// Author       : Thomas Wilkens
// Task         : This function checks if the given value is a valid time or time range.
// Parameters   : timeRange - [in] The value which shall be checked.
// Return Value : OFTrue  - The given value is a valid time or time range.
//                OFFalse - The given value is not a valid time or time range.
{
  // create new string without leading or trailing blanks
  OFString timeRange = DeleteLeadingAndTrailingBlanks( value );

  // check if string is empty now
  if( timeRange.empty() )
    return( OFFalse );

  // check if only allowed characters occur in the string
  if( !ContainsOnlyValidCharacters( timeRange.c_str(), "0123456789.:-" ) )
    return( OFFalse );

  // Determine if a hyphen occurs in the time range
  size_t hyphen = timeRange.find('-');
  if( hyphen != OFString_npos )
  {
    // determine if two time values are given or not
    if( timeRange[0] == '-' )
    {
      // if the hyphen occurs at the beginning, there is just one time value which has to be checked for validity
      return IsValidTime( timeRange.substr(1) );
    }
    else if( timeRange[ timeRange.length() - 1 ] == '-' )
    {
      // if the hyphen occurs at the end, there is just one time value which has to be checked for validity
      return IsValidTime( timeRange.substr(0, timeRange.length()-1) );
    }
    else
    {
      // in this case the hyphen occurs somewhere in between beginning and end; hence there are two time values
      // which have to be checked for validity.

      // check both times for validity
      if( IsValidTime( timeRange.substr(0, timeRange.length() - hyphen -1 )) &&
          IsValidTime( timeRange.substr( hyphen + 1 )                     ))
        return OFTrue;
    }
  }
  else
  {
    // if there is no hyphen, there is just one date value which has to be checked for validity
    return IsValidTime( timeRange );
  }
  return OFFalse;
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::IsValidTime( const OFString& value )
// Date         : March 19, 2002
// Author       : Thomas Wilkens
// Task         : This function checks if the given time value is valid.
//                According to the 2001 DICOM standard, part 5, Table 6.2-1, a time
//                value is either in format "hhmmss.fracxx" or "hh:mm:ss.fracxx" where
//                 - hh represents the hour (0-23)
//                 - mm represents the minutes (0-59)
//                 - ss represents the seconds (0-59)
//                 - fracxx represents the fraction of a second in millionths of seconds (000000-999999)
//                Note that one or more of the components mm, ss, or fracxx may be missing as
//                long as every component to the right of a missing component is also missing.
//                If fracxx is missing, the "." character in front of fracxx is also missing.
// Parameters   : value - [in] The value which shall be checked.
// Return Value : OFTrue  - Time is valid.
//                OFFalse - Time is not valid.
{
  int hour=0, min=0, sec=0, frac=0, fieldsRead=0;
  // create new string without leading or trailing blanks
  OFString timevalue = DeleteLeadingAndTrailingBlanks( value );

  // check if string is empty now
  if( timevalue.empty() )
    return( OFFalse );

  // check if only allowed characters occur in the string
  if( !ContainsOnlyValidCharacters( timevalue.c_str(), "0123456789.:" ) )
    return( OFFalse );

  // check which of the two formats applies to the given string
  size_t colon = timevalue.find(':');
  if( colon != OFString_npos )
  {
    // time format is "hh:mm:ss.fracxx"

    // check which components are missing
    if( timevalue.length() == 5 )
    {
      // scan given time string "hh:mm"
      fieldsRead = sscanf( timevalue.c_str(), "%2d:%2d", &hour, &min );
      if( fieldsRead == 2 && hour >= 0 && hour <= 23 && min >= 0 && min <= 59 )
        return OFTrue;
    }
    else if( timevalue.length() == 8 )
    {
      // scan given time string "hh:mm:ss"
      fieldsRead = sscanf( timevalue.c_str(), "%2d:%2d:%2d", &hour, &min, &sec );
      if( fieldsRead == 3 && hour >= 0 && hour <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59 )
        return OFTrue;
    }
    else if( timevalue.length() > 8 && timevalue.length() < 16 )
    {
      // scan given time string "hh:mm:ss.fracxx"
      fieldsRead = sscanf( timevalue.c_str(), "%2d:%2d:%2d.%6d", &hour, &min, &sec, &frac );
      if( fieldsRead == 4 && hour >= 0 && hour <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59 && frac >= 0 && frac <= 999999 )
        return OFTrue;
    }
  }
  else
  {
    // time format is "hhmmss.fracxx"

    // check which components are missing
    if( timevalue.length() == 4 )
    {
      // scan given time string "hhmm"
      fieldsRead = sscanf( timevalue.c_str(), "%2d%2d", &hour, &min );
      if( fieldsRead == 2 && hour >= 0 && hour <= 23 && min >= 0 && min <= 59 )
        return OFTrue;
    }
    else if( timevalue.length() == 6 )
    {
      // scan given time string "hhmmss"
      fieldsRead = sscanf( timevalue.c_str(), "%2d%2d%2d", &hour, &min, &sec );
      if( fieldsRead == 3 && hour >= 0 && hour <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59 )
        return OFTrue;
    }
    else if( timevalue.length() > 6 && timevalue.length() < 14 )
    {
      // scan given time string "hhmmss.fracxx"
      fieldsRead = sscanf( timevalue.c_str(), "%2d%2d%2d.%6d", &hour, &min, &sec, &frac );
      if( fieldsRead == 4 && hour >= 0 && hour <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59 && frac >= 0 && frac <= 999999 )
        return OFTrue;
    }
  }

 return OFFalse;
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::ContainsOnlyValidCharacters( const char *s, const char *charset )
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : This function returns OFTrue if all the characters of s can be found
//                in the string charset.
// Parameters   : s       - [in] String which shall be checked.
//                charset - [in] Possible character set for s. (valid pointer expected.)
// Return Value : This function returns OFTrue if all the characters of s can be found
//                in the string charset. If s equals NULL, OFTrue will be returned.
{
  OFBool result = OFTrue;

  // check parameter
  if( s == NULL )
  {
    return OFTrue;
  }

  // return OFTrue if all the characters of s can be found in the string charset.
  size_t s_len = strlen( s );
  size_t charset_len = strlen( charset );
  for( size_t i=0 ; i<s_len && result ; i++ )
  {
    OFBool isSetMember = OFFalse;
    for( size_t j=0 ; !isSetMember && j<charset_len ; j++ )
    {
      if( s[i] == charset[j] )
        isSetMember = OFTrue;
    }

    if( !isSetMember )
      result = OFFalse;
  }

  return( result );
}

// ----------------------------------------------------------------------------

OFString WlmDataSource::DeleteLeadingAndTrailingBlanks( const OFString& value )
// Date         : March 19, 2002
// Author       : Thomas Wilkens
// Task         : This function makes a copy of value without leading and trailing blanks.
// Parameters   : value - [in] The source string.
// Return Value : A copy of the given string without leading and trailing blanks.
{
  OFString returnValue = value;
  size_t pos = 0;

  // delete leading blanks
  while ( !returnValue.empty() && (returnValue[pos] == ' ') )
    pos++; // count blanks
  if (pos > 0)
    returnValue.erase(0, pos);

  // delete trailing blanks, start from end of string
  pos = returnValue.length() - 1;
  while ( !returnValue.empty() && (returnValue[pos] == ' ') )
    pos--;
  if (pos < returnValue.length() -1)
    returnValue.erase(pos);

  return returnValue;
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::GetStringValue( const DcmElement *elem,
                                      OFString& resultVal )
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : This function returns the value of the given DICOM string element (attribute)
//                in the parameter resultVal and returns OFTrue if successful.
//                If the element does not refer to a string attribute or contains no value,
//                OFFalse is returned.
// Parameters   : elem - [in] The DICOM element.
//                resultVal - [out] The resulting string value
// Return Value : OFTrue if string value could be accessed, OFFalse else
{
  DcmElement *elemNonConst = OFconst_cast(DcmElement*, elem);
  OFCondition result = elemNonConst->getOFStringArray( resultVal );
  if( result.bad() || resultVal.empty() )
    return OFFalse;
  return OFTrue;
}

// ----------------------------------------------------------------------------

DcmAttributeTag *WlmDataSource::GetOffendingElements()
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : This function returns offending elements from member variable offendingElements
//                if there are any.
// Parameters   : none.
// Return Value : Pointer to offending elements or NULL if there are none.
{
  if( offendingElements->getLength() == 0 )
    return NULL;
  else
    return offendingElements;
}

// ----------------------------------------------------------------------------

DcmLongString *WlmDataSource::GetErrorComments()
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : This function returns error comments from member variable errorComment
//                if there are any.
// Parameters   : none.
// Return Value : Pointer to error comments or NULL if there are none.
{
  if( errorComment->getLength() == 0 )
    return NULL;
  else
    return errorComment;
}

// ----------------------------------------------------------------------------

WlmDataSourceStatusType WlmDataSource::CancelFindRequest()
// Date         : December 10, 2001
// Author       : Thomas Wilkens
// Task         : This function handles a C-CANCEL Request during the processing of a C-FIND Request.
//                In detail, in case there are still matching datasets captured in member variable
//                matchingDatasets, memory for these datasets (and the array itself) is freed and
//                all pointers are set to NULL.
// Parameters   : none.
// Return Value : WLM_CANCEL.
{
  // remove all remaining datasets from result list
  while (!matchingDatasets.empty())
  {
    DcmDataset *dset = matchingDatasets.front();
    delete dset; dset = NULL;
    matchingDatasets.pop_front();
  }

  // return WLM_CANCEL
  return WLM_CANCEL;
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::IsSupportedMatchingKeyAttribute( DcmElement *element, DcmSequenceOfItems *supSequenceElement )
// Date         : December 12, 2001
// Author       : Thomas Wilkens
// Task         : This function checks if the given element refers to an attribute which is a supported
//                matching key attribute. If this is the case OFTrue is returned, else OFFalse.
//                Currently, the following attributes are supported as matching keys:
//                   DCM_ScheduledProcedureStepSequence                    (0040,0100)  SQ  R  1
//                    > DCM_ScheduledStationAETitle                        (0040,0001)  AE  R  1
//                    > DCM_ScheduledProcedureStepStartDate                (0040,0002)  DA  R  1
//                    > DCM_ScheduledProcedureStepStartTime                (0040,0003)  TM  R  1
//                    > DCM_Modality                                       (0008,0060)  CS  R  1
//                    > DCM_ScheduledPerformingPhysicianName               (0040,0006)  PN  R  2
//                   DCM_PatientName                                       (0010,0010)  PN  R  1
//                   DCM_ResponsiblePerson                                 (0010,2297)  PN  O  3
//                   DCM_ResponsiblePersonRole                             (0010,2298)  CS  O  3
//                   DCM_PatientID                                         (0010,0020)  LO  R  1
//                   DCM_AccessionNumber                                   (0008,0050)  SH  O  2
//                   DCM_RequestedProcedureID                              (0040,1001)  SH  O  1
//                   DCM_ReferringPhysicianName                            (0008,0090)  PN  O  2
//                   DCM_PatientSex                                        (0010,0040)  CS  O  2
//                   DCM_RequestingPhysician                               (0032,1032)  PN  O  2
//                   DCM_AdmissionID                                       (0038,0010)  LO  O  2
//                   DCM_RequestedProcedurePriority                        (0040,1003)  SH  O  2
//                   DCM_PatientBirthDate                                  (0010,0030)  DA  O  2
//                   DCM_IssuerOfPatientID                                 (0010,0021)  LO  O  3
//                   DCM_StudyDate                                         (0008,0020)  DA  O  3
//                   DCM_StudyTime                                         (0008,0030)  TM  O  3
// Parameters   : element            - [in] Pointer to the element which shall be checked.
//                supSequenceElement - [in] Pointer to the superordinate sequence element of which
//                                     the currently processed element is an attribute, or NULL if
//                                     the currently processed element does not belong to any sequence.
// Return Value : OFTrue  - The given tag is a supported matching key attribute.
//                OFFalse - The given tag is not a supported matching key attribute.
{
  DcmTagKey elementKey, supSequenceElementKey;

  // determine the current element's tag
  elementKey = element->getTag().getXTag();

  // determine the sequence element's tag, if there is one
  if( supSequenceElement != NULL )
    supSequenceElementKey = supSequenceElement->getTag().getXTag();

  // initialize result variable
  OFBool isSupportedMatchingKeyAttribute = OFFalse;

  // perform check, depending on if a superordinate sequence element is given or not
  if( supSequenceElement != NULL )
  {
    if( supSequenceElementKey == DCM_ScheduledProcedureStepSequence &&
        ( elementKey == DCM_ScheduledStationAETitle           ||
          elementKey == DCM_ScheduledProcedureStepStartDate   ||
          elementKey == DCM_ScheduledProcedureStepStartTime   ||
          elementKey == DCM_Modality                          ||
          elementKey == DCM_ScheduledPerformingPhysicianName ) )
      isSupportedMatchingKeyAttribute = OFTrue;
  }
  else
  {
    if( elementKey == DCM_ScheduledProcedureStepSequence ||
        elementKey == DCM_PatientName                    ||
        elementKey == DCM_ResponsiblePerson              ||
        elementKey == DCM_ResponsiblePersonRole          ||
        elementKey == DCM_PatientID                      ||
        elementKey == DCM_IssuerOfPatientID              ||
        elementKey == DCM_StudyDate                      ||
        elementKey == DCM_StudyTime                      ||
        elementKey == DCM_AccessionNumber                ||
        elementKey == DCM_RequestedProcedureID           ||
        elementKey == DCM_ReferringPhysicianName         ||
        elementKey == DCM_PatientSex                     ||
        elementKey == DCM_RequestingPhysician            ||
        elementKey == DCM_AdmissionID                    ||
        elementKey == DCM_RequestedProcedurePriority     ||
        elementKey == DCM_PatientBirthDate )
      isSupportedMatchingKeyAttribute = OFTrue;
  }

  // return result
  return( isSupportedMatchingKeyAttribute );
}

// ----------------------------------------------------------------------------

OFBool WlmDataSource::IsSupportedReturnKeyAttribute( DcmElement *element, DcmSequenceOfItems *supSequenceElement )
// Date         : December 12, 2001
// Author       : Thomas Wilkens
// Task         : This function checks if the given element refers to an attribute which is a supported
//                return key attribute. If this is the case OFTrue is returned, else OFFalse.
//                Currently, the following attributes are supported as return keys:
//                   DCM_ScheduledProcedureStepSequence                    (0040,0100)  SQ  R  1
//                    > DCM_ScheduledStationAETitle                        (0040,0001)  AE  R  1
//                    > DCM_ScheduledProcedureStepStartDate                (0040,0002)  DA  R  1
//                    > DCM_ScheduledProcedureStepStartTime                (0040,0003)  TM  R  1
//                    > DCM_Modality                                       (0008,0060)  CS  R  1
//                    > DCM_ScheduledPerformingPhysicianName               (0040,0006)  PN  R  2
//                    > DCM_ScheduledProcedureStepDescription              (0040,0007)  LO  O  1
//                    > DCM_ScheduledStationName                           (0040,0010)  SH  O  2
//                    > DCM_ScheduledProcedureStepLocation                 (0040,0011)  SH  O  2
//                    > DCM_PreMedication                                  (0040,0012)  LO  O  2
//                    > DCM_ScheduledProcedureStepID                       (0040,0009)  SH  O  1
//                    > DCM_RequestedContrastAgent                         (0032,1070)  LO  O  2
//                    > DCM_CommentsOnTheScheduledProcedureStep            (0040,0400)  LT  O  3  (from the Scheduled Procedure Step Module)
//                    > DCM_ScheduledProcedureStepStatus                   (0040,0020)  CS  O  3
//                    > DCM_ScheduledProcedureStepEndDate                  (0040,0004)  DA  O  3  (from the Scheduled Procedure Step Module)
//                    > DCM_ScheduledProcedureStepEndTime                  (0040,0005)  TM  O  3  (from the Scheduled Procedure Step Module)
//                    > DCM_ScheduledProtocolCodeSequence                  (0040,0008)  SQ  O  1C
//                    > > DCM_CodeValue                                    (0008,0100)  SH  O  1C
//                    > > DCM_CodingSchemeVersion                          (0008,0103)  SH  O  3
//                    > > DCM_CodingSchemeDesignator                       (0080,0102)  SH  O  1C
//                    > > DCM_CodeMeaning                                  (0080,0104)  LO  O  3
//                   DCM_RequestedProcedureID                              (0040,1001)  SH  O  1
//                   DCM_RequestedProcedureDescription                     (0032,1060)  LO  O  1
//                   DCM_StudyInstanceUID                                  (0020,000d)  UI  O  1
//                   DCM_StudyDate                                         (0008,0020)  DA  O  3
//                   DCM_StudyTime                                         (0008,0030)  TM  O  3
//                   DCM_ReferencedStudySequence                           (0008,1110)  SQ  O  2
//                    > DCM_ReferencedSOPClassUID                          (0008,1150)  UI  O  1
//                    > DCM_ReferencedSOPInstanceUID                       (0008,1155)  UI  O  1
//                   DCM_RequestedProcedurePriority                        (0040,1003)  SH  O  2
//                   DCM_PatientTransportArrangements                      (0040,1004)  LO  O  2
//                   DCM_AccessionNumber                                   (0008,0050)  SH  O  2
//                   DCM_RequestingPhysician                               (0032,1032)  PN  O  2
//                   DCM_ReferringPhysicianName                            (0008,0090)  PN  O  2
//                   DCM_AdmissionID                                       (0038,0010)  LO  O  2
//                   DCM_CurrentPatientLocation                            (0038,0300)  LO  O  2
//                   DCM_ReferencedPatientSequence                         (0008,1120)  SQ  O  2
//                    > DCM_ReferencedSOPClassUID                          (0008,1150)  UI  O  2
//                    > DCM_ReferencedSOPInstanceUID                       (0008,1155)  UI  O  2
//                   DCM_PatientName                                       (0010,0010)  PN  R  1
//                   DCM_ResponsiblePerson                                 (0010,2297)  PN  O  3
//                   DCM_ResponsiblePersonRole                             (0010,2298)  CS  O  3
//                   DCM_PatientID                                         (0010,0020)  LO  R  1
//                   DCM_IssuerOfPatientID                                 (0010,0021)  LO  O  3  (from the Patient Identification Module)
//                   DCM_PatientBirthDate                                  (0010,0030)  DA  O  2
//                   DCM_PatientSex                                        (0010,0040)  CS  O  2
//                   DCM_PatientWeight                                     (0010,1030)  DS  O  2
//                   DCM_ConfidentialityConstraintOnPatientDataDescription (0040,3001)  LO  O  2
//                   DCM_PatientState                                      (0038,0500)  LO  O  2
//                   DCM_PregnancyStatus                                   (0010,21c0)  US  O  2
//                   DCM_MedicalAlerts                                     (0010,2000)  LO  O  2
//                   DCM_ContrastAllergies                                 (0010,2110)  LO  O  2
//                   DCM_SpecialNeeds                                      (0038,0050)  LO  O  2
//                   DCM_NamesOfIntendedRecipientsOfResults                (0040,1010)  PN  O  3  (from the Requested Procedure Module)
//                   DCM_InstitutionName                                   (0008,0080)  LO  O  3  (from the Visit Identification Module)
//                   DCM_AdmittingDiagnosesDescription                     (0008,1080)  LO  O  3  (from the Visit Admission Module)
//                   DCM_RETIRED_OtherPatientIDs                           (0010,1000)  LO  O  3  (from the Patient Identification Module)
//                   DCM_PatientSize                                       (0010,1020)  DS  O  3  (from the Patient Demographic Module)
//                   DCM_EthnicGroup                                       (0010,2160)  SH  O  3  (from the Patient Demographic Module)
//                   DCM_PatientComments                                   (0010,4000)  LT  O  3  (from the Patient Demographic Module)
//                   DCM_AdditionalPatientHistory                          (0010,21b0)  LT  O  3  (from the Patient Medical Module)
//                   DCM_LastMenstrualDate                                 (0010,21d0)  DA  O  3  (from the Patient Medical Module)
//                   DCM_InstitutionAddress                                (0008,0081)  ST  O  3  (from the Visit Identification Module)
//                   DCM_OtherPatientNames                                 (0010,1001)  PN  O  3  (from the Patient Identification Module)
//                   DCM_PatientAddress                                    (0010,1040)  LO  O  3  (from the Patient Demographic Module)
//                   DCM_MilitaryRank                                      (0010,1080)  LO  O  3  (from the Patient Demographic Module)
//                   DCM_SmokingStatus                                     (0010,21a0)  CS  O  3  (from the Patient Medical Module)
//                   DCM_RequestingService                                 (0032,1033)  LO  O  3  (from the Imaging Service Request Module)
//                   DCM_RETIRED_IssuerOfAdmissionID                       (0038,0011)  LO  O  3  (from the Visit Identification Module)
//                   DCM_ReasonForTheRequestedProcedure                    (0040,1002)  LO  O  3  (from the Requested Procedure Module)
//                   DCM_RequestedProcedureLocation                        (0040,1005)  LO  O  3  (from the Requested Procedure Module)
//                   DCM_ConfidentialityCode                               (0040,1008)  LO  O  3  (from the Requested Procedure Module)
//                   DCM_ReportingPriority                                 (0040,1009)  SH  O  3  (from the Requested Procedure Module)
//                   DCM_RequestedProcedureComments                        (0040,1400)  LT  O  3  (from the Requested Procedure Module)
//                   DCM_RETIRED_ReasonForTheImagingServiceRequest         (0040,2001)  LO  O  3  (from the Imaging Service Request Module)
//                   DCM_IssueDateOfImagingServiceRequest                  (0040,2004)  DA  O  3  (from the Imaging Service Request Module)
//                   DCM_IssueTimeOfImagingServiceRequest                  (0040,2005)  TM  O  3  (from the Imaging Service Request Module)
//                   DCM_OrderEnteredBy                                    (0040,2008)  PN  O  3  (from the Imaging Service Request Module)
//                   DCM_OrderEnterersLocation                             (0040,2009)  SH  O  3  (from the Imaging Service Request Module)
//                   DCM_OrderCallbackPhoneNumber                          (0040,2010)  SH  O  3  (from the Imaging Service Request Module)
//                   DCM_PlacerOrderNumberImagingServiceRequest            (0040,2016)  LO  O  3  (from the Imaging Service Request Module)
//                   DCM_FillerOrderNumberImagingServiceRequest            (0040,2017)  LO  O  3  (from the Imaging Service Request Module)
//                   DCM_ImagingServiceRequestComments                     (0040,2400)  LT  O  3  (from the Imaging Service Request Module)
//                   DCM_RequestedProcedureCodeSequence                    (0032,1064)  SQ  O  3  (from the Requested Procedure Module)
//                    > DCM_CodeValue                                      (0008,0100)  SH  O  1C
//                    > DCM_CodingSchemeVersion                            (0008,0103)  SH  O  3
//                    > DCM_CodingSchemeDesignator                         (0080,0102)  SH  O  1C
//                    > DCM_CodeMeaning                                    (0080,0104)  LO  O  3
// Parameters   : element            - [in] Pointer to the element which shall be checked.
//                supSequenceElement - [in] Pointer to the superordinate sequence element of which
//                                     the currently processed element is an attribute, or NULL if
//                                     the currently processed element does not belong to any sequence.
// Return Value : OFTrue  - The given tag is a supported return key attribute.
//                OFFalse - The given tag is not a supported return key attribute.
{
  DcmTagKey elementKey, supSequenceElementKey;

  // determine the current element's tag
  elementKey = element->getTag().getXTag();

  // determine the sequence element's tag, if there is one
  if( supSequenceElement != NULL )
    supSequenceElementKey = supSequenceElement->getTag().getXTag();

  // initialize result variable
  OFBool isSupportedReturnKeyAttribute = OFFalse;

  // perform check, depending on if a superordinate sequence element is given or not
  if( supSequenceElement != NULL )
  {
    if( ( supSequenceElementKey == DCM_ScheduledProcedureStepSequence &&
          ( elementKey == DCM_ScheduledStationAETitle                    ||
            elementKey == DCM_ScheduledProcedureStepStartDate            ||
            elementKey == DCM_ScheduledProcedureStepStartTime            ||
            elementKey == DCM_Modality                                   ||
            elementKey == DCM_ScheduledPerformingPhysicianName           ||
            elementKey == DCM_ScheduledProcedureStepDescription          ||
            elementKey == DCM_ScheduledStationName                       ||
            elementKey == DCM_ScheduledProcedureStepLocation             ||
            elementKey == DCM_PreMedication                              ||
            elementKey == DCM_ScheduledProcedureStepID                   ||
            elementKey == DCM_RequestedContrastAgent                     ||
            elementKey == DCM_CommentsOnTheScheduledProcedureStep        ||
            elementKey == DCM_ScheduledProcedureStepStatus               ||
            elementKey == DCM_ScheduledProcedureStepEndDate              ||
            elementKey == DCM_ScheduledProcedureStepEndTime              ||
            elementKey == DCM_ScheduledProtocolCodeSequence ) )          ||
        ( supSequenceElementKey == DCM_ReferencedStudySequence        &&
          ( elementKey == DCM_ReferencedSOPClassUID                      ||
            elementKey == DCM_ReferencedSOPInstanceUID ) )               ||
        ( supSequenceElementKey == DCM_ReferencedPatientSequence      &&
          ( elementKey == DCM_ReferencedSOPClassUID                      ||
            elementKey == DCM_ReferencedSOPInstanceUID ) )               ||
        ( supSequenceElementKey == DCM_ScheduledProtocolCodeSequence  &&
          ( elementKey == DCM_CodeValue                                  ||
            elementKey == DCM_CodingSchemeVersion                        ||
            elementKey == DCM_CodingSchemeDesignator                     ||
            elementKey == DCM_CodeMeaning ) )                            ||
        ( supSequenceElementKey == DCM_RequestedProcedureCodeSequence &&
          ( elementKey == DCM_CodeValue                                  ||
            elementKey == DCM_CodingSchemeVersion                        ||
            elementKey == DCM_CodingSchemeDesignator                     ||
            elementKey == DCM_CodeMeaning ) ) )
      isSupportedReturnKeyAttribute = OFTrue;
  }
  else
  {
    if( elementKey == DCM_ScheduledProcedureStepSequence                    ||
        elementKey == DCM_RequestedProcedureID                              ||
        elementKey == DCM_RequestedProcedureDescription                     ||
        elementKey == DCM_StudyInstanceUID                                  ||
        elementKey == DCM_StudyDate                                         ||
        elementKey == DCM_StudyTime                                         ||
        elementKey == DCM_ReferencedStudySequence                           ||
        elementKey == DCM_RequestedProcedurePriority                        ||
        elementKey == DCM_PatientTransportArrangements                      ||
        elementKey == DCM_AccessionNumber                                   ||
        elementKey == DCM_RequestingPhysician                               ||
        elementKey == DCM_ReferringPhysicianName                            ||
        elementKey == DCM_AdmissionID                                       ||
        elementKey == DCM_CurrentPatientLocation                            ||
        elementKey == DCM_ReferencedPatientSequence                         ||
        elementKey == DCM_PatientName                                       ||
        elementKey == DCM_ResponsiblePerson                                 ||
        elementKey == DCM_ResponsiblePersonRole                             ||
        elementKey == DCM_PatientID                                         ||
        elementKey == DCM_IssuerOfPatientID                                 ||
        elementKey == DCM_PatientBirthDate                                  ||
        elementKey == DCM_PatientSex                                        ||
        elementKey == DCM_PatientWeight                                     ||
        elementKey == DCM_ConfidentialityConstraintOnPatientDataDescription ||
        elementKey == DCM_PatientState                                      ||
        elementKey == DCM_PregnancyStatus                                   ||
        elementKey == DCM_MedicalAlerts                                     ||
        elementKey == DCM_Allergies                                         ||
        elementKey == DCM_SpecialNeeds                                      ||
        elementKey == DCM_NamesOfIntendedRecipientsOfResults                ||
        elementKey == DCM_InstitutionName                                   ||
        elementKey == DCM_AdmittingDiagnosesDescription                     ||
        elementKey == DCM_RETIRED_OtherPatientIDs                           ||
        elementKey == DCM_PatientSize                                       ||
        elementKey == DCM_EthnicGroup                                       ||
        elementKey == DCM_PatientComments                                   ||
        elementKey == DCM_AdditionalPatientHistory                          ||
        elementKey == DCM_LastMenstrualDate                                 ||
        elementKey == DCM_InstitutionAddress                                ||
        elementKey == DCM_OtherPatientNames                                 ||
        elementKey == DCM_PatientAddress                                    ||
        elementKey == DCM_MilitaryRank                                      ||
        elementKey == DCM_SmokingStatus                                     ||
        elementKey == DCM_RequestingService                                 ||
        elementKey == DCM_RETIRED_IssuerOfAdmissionID                       ||
        elementKey == DCM_ReasonForTheRequestedProcedure                    ||
        elementKey == DCM_RequestedProcedureLocation                        ||
        elementKey == DCM_ConfidentialityCode                               ||
        elementKey == DCM_ReportingPriority                                 ||
        elementKey == DCM_RequestedProcedureComments                        ||
        elementKey == DCM_RETIRED_ReasonForTheImagingServiceRequest         ||
        elementKey == DCM_IssueDateOfImagingServiceRequest                  ||
        elementKey == DCM_IssueTimeOfImagingServiceRequest                  ||
        elementKey == DCM_OrderEnteredBy                                    ||
        elementKey == DCM_OrderEntererLocation                              ||
        elementKey == DCM_OrderCallbackPhoneNumber                          ||
        elementKey == DCM_PlacerOrderNumberImagingServiceRequest            ||
        elementKey == DCM_FillerOrderNumberImagingServiceRequest            ||
        elementKey == DCM_ImagingServiceRequestComments                     ||
        elementKey == DCM_RequestedProcedureCodeSequence )
      isSupportedReturnKeyAttribute = OFTrue;
  }

  // return result
  return( isSupportedReturnKeyAttribute );
}
