/// <summary>
        /// Processes the attributes.
        /// </summary>
        /// <param name="attributeConcepts">
        /// The attribute concepts.
        /// </param>
        /// <param name="attributes">
        /// The attributes.
        /// </param>
        /// <exception cref="SdmxSemmanticException">
        /// Unknown attribute ' + attribute + ' reported in the data.  This attribute is
        ///     not defined by the data structure definition
        /// </exception>
        private void ProcessAttributes(ICollection<string> attributeConcepts, ICollection<IKeyValue> attributes)
        {
            foreach (var attributeConcept in attributeConcepts)
            {
                string conceptValue;
                if ((this._attributeValues.TryGetValue(attributeConcept, out conceptValue) || this._rolledUpAttributes.TryGetValue(attributeConcept, out conceptValue)) && conceptValue != null)
                {
                    IKeyValue kv = new KeyValueImpl(conceptValue, attributeConcept);
                    attributes.Add(kv);
                }
            }

            if (this._attributeValues.Count != attributes.Count)
            {
                string attribute = this._attributeValues.Keys.FirstOrDefault(s => !attributeConcepts.Contains(s));
                if (attribute != null)
                {
                    throw new SdmxSemmanticException("Unknown attribute '" + attribute + "' reported in the data.  This attribute is not defined by the data structure definition");
                }
            }
        }
        /// <summary>
        ///     Processes the series node.
        /// </summary>
        /// <exception cref="SdmxSemmanticException">Missing series key value</exception>
        /// <returns>
        ///     The <see cref="IKeyable" />.
        /// </returns>
        protected override IKeyable ProcessSeriesNode()
        {
            // clear values
            this._keyValues.Clear();
            this._attributeValues.Clear();
            TimeFormat timeFormat = null;
            string timeValue = null;
            ISet<string> unknownConcepts = new HashSet<string>(StringComparer.Ordinal);
            for (int i = 0; i < this.Parser.AttributeCount; i++)
            {
                this.Parser.MoveToAttribute(i);
                string attributeId = this.GetComponentId(this.Parser.LocalName);
                string attributeValue = this.Parser.Value;
                if (string.Equals(attributeId, this._timeConcept))
                {
                    timeValue = attributeValue;
                    timeFormat = DateUtil.GetTimeFormatOfDate(timeValue);
                }
                else if (this._dimensionConcepts.Contains(attributeId))
                {
                    this._keyValues.Add(attributeId, attributeValue);
                }
                else if (this._seriesAttributes.Contains(attributeId))
                {
                    this._attributeValues.Add(attributeId, attributeValue);
                }
                else if (this.DatasetPositionInternal == Api.Constants.DatasetPosition.ObservationAsSeries)
                {
                    // This attribute was not found as a series level attribute, it could still be an observation level attribute
                    // But this is only allowed if we are processing this series node as a FLAT obs node which is both an obs and key
                    // In which case this attributeName could be an observation attribute, the primary measure, or the time concept
                    if (!this._observationAttributes.Contains(attributeId) && !attributeId.Equals(this._primaryMeasureConcept) && !attributeId.Equals(this._timeConcept))
                    {
                        unknownConcepts.Add(attributeId);
                    }
                }
                else
                {
                    unknownConcepts.Add(attributeId);
                }
            }

            var key = new List<IKeyValue>();
            foreach (var dimensionConcept in this._dimensionConcepts)
            {
                string conceptValue;
                if (!this._keyValues.TryGetValue(dimensionConcept, out conceptValue) && !this._rolledUpAttributes.TryGetValue(dimensionConcept, out conceptValue))
                {
                    if (this.DatasetHeader.Action != DatasetActionEnumType.Delete)
                    {
                        if (this.IsTimeSeries || !this.CrossSectionConcept.Equals(dimensionConcept))
                        {
                            throw new SdmxSemmanticException(string.Format("Missing series key value for concept: {0}", dimensionConcept));
                        }
                    }
                }
                else
                {
                    IKeyValue kv = new KeyValueImpl(conceptValue, dimensionConcept);
                    key.Add(kv);
                }
            }

            if (unknownConcepts.Count > 0)
            {
                // NOTE this is a simplified version of the corresponding Java code. 
                string series = string.Join(", ", key.Select(kv => kv.Code));
                string unknownConcept = string.Join(", ", unknownConcepts);
                throw new SdmxSemmanticException(string.Format(CultureInfo.InvariantCulture, "Unknown concept(s) '{0}' reported for series : {1}", unknownConcept, series));
            }

            var attributes = new List<IKeyValue>();
            try
            {
                this.ProcessAttributes(this._seriesAttributes, attributes);
            }
            catch (SdmxException e)
            {
                throw new SdmxException("Error while processing series attributes", e);
            }

            // Clear values
            this._keyValues.Clear();
            this._attributeValues.Clear();

            if (this.IsTimeSeries)
            {
                if (this.DatasetPositionInternal == Api.Constants.DatasetPosition.Series)
                {
                    // NOTE Java has a try catch because it needs change the exception to a runtime exception. In .NET we should not do this because 
                    // all exceptions in C# are run time exceptions. Doing it makes it harder to diagnose problems. 
                    while (this.RunAheadParser.Read())
                    {
                        var nodeType = this.RunAheadParser.NodeType;
                        string localName = this.RunAheadParser.LocalName;
                        if (nodeType == XmlNodeType.Element)
                        {
                            if (ElementNameTable.Obs.Is(localName))
                            {
                                this.ProcessObservation(this.RunAheadParser);
                                timeFormat = DateUtil.GetTimeFormatOfDate(this._obsTime);
                                break;
                            }
                        }
                        else if (nodeType == XmlNodeType.EndElement)
                        {
                            if (ElementNameTable.Series.Is(localName))
                            {
                                break;
                            }
                        }
                    }
                }

                this.CurrentKeyValue = this.CreateKeyable(key, attributes, timeFormat);
            }
            else
            {
                this.CurrentKeyValue = this.CreateKeyable(key, attributes, timeFormat, timeValue);
            }

            if (this.DatasetPositionInternal == Api.Constants.DatasetPosition.ObservationAsSeries)
            {
                this.CurrentObs = this.ProcessObsNode(this.Parser);

                // unused variable in java 1.1.4. Possibly a bug in Java SdmxSource 1.1.4?
                timeFormat = this.CurrentObs.ObsTimeFormat;
            }

            this._attributeValues.Clear();
            return this.CurrentKeyValue;
        }
        /// <summary>
        ///     Processes the group node.
        /// </summary>
        /// <returns>
        ///     The <see cref="IKeyable" />.
        /// </returns>
        protected override IKeyable ProcessGroupNode()
        {
            // clear values
            this._keyValues.Clear();
            this._attributes.Clear();

            for (int i = 0; i < this.Parser.AttributeCount; i++)
            {
                this.Parser.MoveToAttribute(i);
                string attributeName = this.GetComponentId(this.Parser.LocalName);
                string attributeValue = this.Parser.Value;
                string ns = this.Parser.NamespaceURI;

                if (XmlConstants.XmlSchemaNS.Equals(ns) && "type".Equals(attributeName))
                {
                    this.GroupId = attributeValue.Contains(":") ? attributeValue.Split(':')[1] : attributeValue;
                }
                else
                {
                    if (this._dimensionConcepts.Contains(attributeName))
                    {
                        this._keyValues.Add(attributeName, attributeValue);
                    }
                    else
                    {
                        this._attributeValues.Add(attributeName, attributeValue);
                    }
                }
            }

            var key = new List<IKeyValue>();
            var attributes = new List<IKeyValue>();

            List<string> conceptList;
            if (!this._groupConcepts.TryGetValue(this.GroupId, out conceptList))
            {
                throw new SdmxSemmanticException(string.Format(CultureInfo.InvariantCulture, "Data Structure '{0}' does not contain group '{1}'", this.CurrentDsdInternal, this.GroupId));
            }

            foreach (var groupConcept in conceptList)
            {
                string conceptValue;
                if (!this._keyValues.TryGetValue(groupConcept, out conceptValue) && !this._rolledUpAttributes.TryGetValue(groupConcept, out conceptValue))
                {
                    throw new SdmxSemmanticException(string.Format(CultureInfo.InvariantCulture, "No value found in data for group '{0}' and concept '{1}'.  ", this.GroupId, groupConcept));
                }

                var kv = new KeyValueImpl(conceptValue, groupConcept);
                key.Add(kv);
            }

            try
            {
                this.ProcessAttributes(this._groupAttributeConcepts[this.GroupId], attributes);
            }
            catch (SdmxSemmanticException e)
            {
                throw new SdmxSemmanticException(string.Format("Error while processing group attributes for group '{0}' ", this.GroupId), e);
            }

            this._keyValues.Clear();
            this._attributeValues.Clear();

            return this.CurrentKeyValue = this.CreateKeyable(key, attributes, this.GroupId);
        }
        /// <summary>
        ///     Builds the current observation.
        /// </summary>
        private void BuildCurrentObservation()
        {
            var attributes = new List<IKeyValue>();

            foreach (var key in this._observationAttributes)
            {
                IKeyValue keyValue = new KeyValueImpl(key.Value, key.Key);
                attributes.Add(keyValue);
            }

            IKeyValue crossSectionValue = null;
            if (this._crossSectionalObservation != null)
            {
                crossSectionValue = new KeyValueImpl(this._crossSectionalObservation, this._availableMeasureDimensions[0]);
            }

            this._currentObs = new ObservationImpl(this._currentKey, this._currentKey.ObsTime, this._observationValue, attributes, crossSectionValue);
        }
        /// <summary>
        ///     Builds the current key.
        /// </summary>
        private void BuildCurrentKey()
        {
            var conceptKeys = new List<IKeyValue>();
            foreach (var key in this._dataSetConcepts)
            {
                IKeyValue keyValue = new KeyValueImpl(key.Value, key.Key);
                conceptKeys.Add(keyValue);
            }

            foreach (var key in this._groupConcepts)
            {
                IKeyValue keyValue = new KeyValueImpl(key.Value, key.Key);
                conceptKeys.Add(keyValue);
            }

            foreach (var key in this._sectionConcepts)
            {
                IKeyValue keyValue = new KeyValueImpl(key.Value, key.Key);
                conceptKeys.Add(keyValue);
            }

            foreach (var key in this._observationConcepts)
            {
                IKeyValue keyValue = new KeyValueImpl(key.Value, key.Key);
                conceptKeys.Add(keyValue);
            }

            var attributeKeys = new List<IKeyValue>();
            foreach (var key in this._dataSetAttributes)
            {
                IKeyValue keyValue = new KeyValueImpl(key.Value, key.Key);
                attributeKeys.Add(keyValue);
            }

            foreach (var key in this._groupAttributes)
            {
                IKeyValue keyValue = new KeyValueImpl(key.Value, key.Key);
                attributeKeys.Add(keyValue);
            }

            foreach (var key in this._sectionAttributes)
            {
                IKeyValue keyValue = new KeyValueImpl(key.Value, key.Key);
                attributeKeys.Add(keyValue);
            }

            if (!string.IsNullOrWhiteSpace(this._timeDimensionId) && !string.IsNullOrWhiteSpace(this._obsTime))
            {
                TimeFormat timeFormat = DateUtil.GetTimeFormatOfDate(this._obsTime);
                this._currentKey = new KeyableImpl(this._dataflow, this._currentDsd, conceptKeys, attributeKeys, timeFormat, this._timeDimensionId, this._obsTime);
            }
            else
            {
                this._currentKey = new KeyableImpl(this._dataflow, this._currentDsd, conceptKeys, attributeKeys, string.Empty);
            }
        }
        /// <summary>
        /// Processes the OBS node.
        /// </summary>
        /// <param name="parser">
        /// The parser.
        /// </param>
        /// <returns>
        /// The <see cref="IObservation"/>.
        /// </returns>
        protected override IObservation ProcessObsNode(XmlReader parser)
        {
            string obsDimension = null;
            string obsValue = null;
            IList<IKeyValue> attributes = null;
            string text = null;
            while (parser.Read())
            {
                var nodeType = parser.NodeType;
                if (nodeType == XmlNodeType.Element)
                {
                    string nodeName = parser.LocalName;
                    if (ElementNameTable.ObsDimension.Is(nodeName))
                    {
                        obsDimension = parser.GetAttribute(AttributeNameTable.value);
                    }
                    else if (ElementNameTable.ObsValue.Is(nodeName))
                    {
                        obsValue = parser.GetAttribute(AttributeNameTable.value);
                    }
                    else if (ElementNameTable.Attributes.Is(nodeName))
                    {
                        attributes = this.GetKeyValues(ElementNameTable.Attributes);
                    }
                }
                else if (nodeType == XmlNodeType.Text)
                {
                    text = parser.Value;
                }
                else if (nodeType == XmlNodeType.EndElement)
                {
                    string nodeName = parser.LocalName;
                    if (ElementNameTable.Time.Is(nodeName))
                    {
                        obsDimension = text;
                    }
                    else if (ElementNameTable.Obs.Is(nodeName))
                    {
                        break;
                    }
                }
            }

            obsDimension = this.GetComponentId(obsDimension);

            try
            {
                if (this.IsTimeSeries)
                {
                    return new ObservationImpl(this.CurrentKeyValue, obsDimension, obsValue, attributes);
                }

                if (obsDimension == null)
                {
                    throw new SdmxSemmanticException(
                        string.Format("Error while processing observation for series '{0}' , missing required cross sectional concept value '{1}'", this.CurrentKey, this.CrossSectionConcept));
                }

                IKeyValue crossSection = new KeyValueImpl(obsDimension, this.CrossSectionConcept);
                return new ObservationImpl(this.CurrentKeyValue, this.CurrentKeyValue.ObsTime, obsValue, attributes, crossSection);
            }
            catch (SdmxException)
            {
                throw;
            }
            catch (Exception th)
            {
                if (this.CurrentKey != null)
                {
                    throw new SdmxException("Error while processing observation for key " + this.CurrentKeyValue, th);
                }

                throw new SdmxException("Error while processing observation");
            }
        }