/// <summary>
        /// Imprime um item tabular para o elemento da tabela.
        /// </summary>
        /// <typeparam name="R">O tipo dos objectos que constituem as linhas do item tabular.</typeparam>
        /// <typeparam name="L">O tipo dos objectos que constituem as células do item tabular.</typeparam>
        /// <param name="cellElementName">O nome do elemento da célula.</param>
        /// <param name="element">O elemento da tabela que irá conter as linhas.</param>
        /// <param name="columnsNumber">O número de colunas esperado.</param>
        /// <param name="tabularItem">O item tabular.</param>
        /// <param name="mergingRegions">O conjunto de regiões de células fundidas.</param>
        /// <param name="attributessSetter">A função responsável pela adição de atributos.</param>
        /// <param name="elementsSetter">A função responsável pelo processamento de elementos.</param>
        private void PrintTabularItem <R, L>(
            string cellElementName,
            XmlElement element,
            int columnsNumber,
            IGeneralTabularItem <R> tabularItem,
            NonIntersectingMergingRegionsSet <int, MergingRegion <int> > mergingRegions,
            Action <int, AttributeSetter> attributessSetter,
            Action <int, int, object, ElementSetter> elementsSetter)
            where R : IGeneralTabularRow <L>
            where L : IGeneralTabularCell
        {
            var attributeSetter            = new AttributeSetter();
            var validatedCellElementSetter = new ValidatedCellElementSetter();
            var linesNumber = tabularItem.LastRowNumber + 1;
            var linePointer = 0;

            foreach (var tabularLine in tabularItem)
            {
                var currentLine = tabularLine.RowNumber;
                for (; linePointer < currentLine; ++linePointer)
                {
                    var rowElement = element.OwnerDocument.CreateElement("tr");
                    attributeSetter.CurrentElement = rowElement;
                    attributessSetter.Invoke(
                        linePointer,
                        attributeSetter);
                    this.AppendEmptyCells(
                        cellElementName,
                        rowElement,
                        linePointer,
                        columnsNumber,
                        linesNumber,
                        columnsNumber,
                        validatedCellElementSetter,
                        mergingRegions,
                        elementsSetter);
                    element.AppendChild(rowElement);
                }

                var lineElement = element.OwnerDocument.CreateElement("tr");
                attributeSetter.CurrentElement = lineElement;
                attributessSetter.Invoke(
                    linePointer,
                    attributeSetter);
                var currentCellNumber = 0;
                foreach (var tabularCell in tabularLine)
                {
                    if (currentCellNumber <= tabularCell.ColumnNumber)
                    {
                        var emptyCellsCount = tabularCell.ColumnNumber - currentCellNumber;
                        this.AppendEmptyCells(
                            cellElementName,
                            lineElement,
                            linePointer,
                            emptyCellsCount,
                            linesNumber,
                            columnsNumber,
                            validatedCellElementSetter,
                            mergingRegions,
                            elementsSetter);

                        var mergingRegion = mergingRegions.GetMergingRegionForCell(
                            tabularCell.ColumnNumber,
                            currentLine);
                        if (mergingRegion == null)
                        {
                            var tableCellElement = lineElement.OwnerDocument.CreateElement(cellElementName);
                            validatedCellElementSetter.CurrentElement = tableCellElement;
                            lineElement.AppendChild(tableCellElement);

                            elementsSetter.Invoke(
                                currentLine,
                                tabularCell.ColumnNumber,
                                tabularCell.GetCellValue <object>(),
                                validatedCellElementSetter);
                            lineElement.AppendChild(tableCellElement);
                            currentCellNumber = tabularCell.ColumnNumber + 1;
                        }
                        else if (mergingRegion.TopLeftX == tabularCell.ColumnNumber)
                        {
                            var colDifference = mergingRegion.BottomRightX - mergingRegion.TopLeftX;
                            if (mergingRegion.TopLeftY == currentLine)
                            {
                                var tableCellElement = lineElement.OwnerDocument.CreateElement(cellElementName);
                                validatedCellElementSetter.CurrentElement = tableCellElement;
                                lineElement.AppendChild(tableCellElement);
                                elementsSetter.Invoke(
                                    currentLine,
                                    tabularCell.ColumnNumber,
                                    tabularCell.GetCellValue <object>(),
                                    validatedCellElementSetter);

                                if (colDifference > 0)
                                {
                                    var colSpan = Math.Min(
                                        colDifference + 1,
                                        columnsNumber - tabularCell.ColumnNumber);
                                    if (colSpan > 1)
                                    {
                                        tableCellElement.SetAttribute("colspan", colSpan.ToString());
                                    }
                                }

                                var rowDifference = mergingRegion.BottomRightY - mergingRegion.TopLeftY;
                                if (rowDifference > 0)
                                {
                                    var rowSpan = Math.Min(
                                        rowDifference + 1,
                                        linesNumber - currentLine);
                                    if (rowSpan > 1)
                                    {
                                        tableCellElement.SetAttribute("rowspan", rowSpan.ToString());
                                    }
                                }
                            }

                            currentCellNumber = tabularCell.ColumnNumber + colDifference + 1;
                        }
                    }
                }

                var finalEmptyCellsCount = columnsNumber - currentCellNumber;
                this.AppendEmptyCells(
                    cellElementName,
                    lineElement,
                    linePointer,
                    finalEmptyCellsCount,
                    linesNumber,
                    columnsNumber,
                    validatedCellElementSetter,
                    mergingRegions,
                    elementsSetter);

                element.AppendChild(lineElement);
                ++linePointer;
            }

            // Inserção das restantes linhas vazias
            for (; linePointer < linesNumber; ++linePointer)
            {
                var rowElement = element.OwnerDocument.CreateElement("th");
                attributeSetter.CurrentElement = rowElement;
                this.headerRowAttributesSetter.Invoke(
                    linePointer,
                    attributeSetter);
                this.AppendEmptyCells(
                    cellElementName,
                    rowElement,
                    linePointer,
                    columnsNumber,
                    linesNumber,
                    columnsNumber,
                    validatedCellElementSetter,
                    mergingRegions,
                    elementsSetter);
            }
        }
        /// <summary>
        /// Obtém html que representa uma tabela a partir de um leitor <see cref="IDataReader"/>.
        /// </summary>
        /// <param name="datareader">O leitor.</param>
        /// <returns>O html como texto.</returns>
        public string GetHtmlFromDataReader(
            IDataReader datareader)
        {
            if (datareader == null)
            {
                throw new ArgumentNullException("dataReader");
            }
            else
            {
                var tableDocument   = new XmlDocument();
                var attributeSetter = new AttributeSetter();
                var elementSetter   = new ElementSetter();

                // Inicializa a tabela
                var table = tableDocument.CreateElement("table");
                attributeSetter.CurrentElement = table;
                this.tableAttributes.Invoke(attributeSetter);
                tableDocument.AppendChild(table);

                // Cabeçalho
                var tableHeader = tableDocument.CreateElement("thead");
                attributeSetter.CurrentElement = tableHeader;
                this.tableHeaderAttributes.Invoke(attributeSetter);
                table.AppendChild(tableHeader);

                // Linhas do cabeçalho
                var tableRow = tableDocument.CreateElement("tr");
                attributeSetter.CurrentElement = tableRow;
                this.headerRowAttributes.Invoke(attributeSetter);
                tableHeader.AppendChild(tableRow);

                // Colunas do cabeçalho
                var fieldCount = datareader.FieldCount;
                for (int i = 0; i < fieldCount; ++i)
                {
                    var columnName = datareader.GetName(i);
                    var tableCell  = tableDocument.CreateElement("th");
                    elementSetter.CurrentElement = tableCell;
                    this.headerCellElement.Invoke(i, columnName, elementSetter);
                    tableRow.AppendChild(tableCell);
                }

                // Corpo
                var tableBody = tableDocument.CreateElement("tbody");
                attributeSetter.CurrentElement = tableBody;
                this.tableBodyAttributes.Invoke(attributeSetter);
                table.AppendChild(tableBody);

                // Linhas da tabela
                var line = 0;
                while (datareader.Read())
                {
                    tableRow = tableDocument.CreateElement("tr");
                    attributeSetter.CurrentElement = tableRow;
                    this.bodyRowAttributesSetter.Invoke(line, attributeSetter);
                    tableBody.AppendChild(tableRow);

                    for (int i = 0; i < fieldCount; ++i)
                    {
                        var tableCell = tableDocument.CreateElement("td");
                        elementSetter.CurrentElement = tableCell;
                        this.bodyCellElement.Invoke(
                            line,
                            i,
                            datareader.GetValue(i),
                            elementSetter);
                        tableRow.AppendChild(tableCell);
                    }

                    ++line;
                }

                // Escreve o xml
                var resultBuilder          = new StringBuilder();
                XmlWriterSettings settings = new XmlWriterSettings
                {
                    Indent             = this.indent,
                    IndentChars        = "  ",
                    NewLineChars       = "\r\n",
                    NewLineHandling    = NewLineHandling.Replace,
                    OmitXmlDeclaration = true
                };

                using (var writer = XmlWriter.Create(resultBuilder, settings))
                {
                    tableDocument.Save(writer);
                }

                return(resultBuilder.ToString());
            }
        }
        /// <summary>
        /// Obtém o html que representa uma tabela a partir de um leitor <see cref="ITabularItem"/>.
        /// </summary>
        /// <typeparam name="R">O tipo dos objectos que constituem as linhas.</typeparam>
        /// <typeparam name="L">O tipo dos objectos que constituem as células.</typeparam>
        /// <param name="tabularItemHeader">O item tabular que contém o cabeçalho.</param>
        /// <param name="tabularItemBody">O item tabular que constitui o corpo.</param>
        /// <returns>O html como texto.</returns>
        public string GetHtmlFromTableItem <R, L>(
            IGeneralTabularItem <R> tabularItemHeader,
            IGeneralTabularItem <R> tabularItemBody)
            where R : IGeneralTabularRow <L>
            where L : IGeneralTabularCell
        {
            if (tabularItemHeader == null)
            {
                throw new ArgumentNullException("tabularItemHeader");
            }
            else if (headerMergingRegions == null)
            {
                throw new ArgumentNullException("tabularBody");
            }
            else
            {
                var columnsNumber = Math.Max(
                    this.GetMaxLastColumnNumber <R, L>(tabularItemHeader),
                    this.GetMaxLastColumnNumber <R, L>(tabularItemBody)) + 1;
                var tableDocument   = new XmlDocument();
                var attributeSetter = new AttributeSetter();
                var elementSetter   = new ElementSetter();

                // Inicializa a tabela
                var table = tableDocument.CreateElement("table");
                attributeSetter.CurrentElement = table;
                this.tableAttributes.Invoke(attributeSetter);
                tableDocument.AppendChild(table);

                // Cabeçalho
                var tableHeader = tableDocument.CreateElement("thead");
                attributeSetter.CurrentElement = tableHeader;
                this.tableHeaderAttributes.Invoke(attributeSetter);
                table.AppendChild(tableHeader);

                this.PrintTabularItem <R, L>(
                    "th",
                    tableHeader,
                    columnsNumber,
                    tabularItemHeader,
                    this.headerMergingRegions,
                    this.headerRowAttributesSetter,
                    this.headerCellElement);

                // Corpo
                var tableBody = tableDocument.CreateElement("tbody");
                attributeSetter.CurrentElement = tableBody;
                this.tableBodyAttributes.Invoke(attributeSetter);
                table.AppendChild(tableBody);

                this.PrintTabularItem <R, L>(
                    "td",
                    tableBody,
                    columnsNumber,
                    tabularItemBody,
                    this.bodyMergingRegions,
                    this.bodyRowAttributesSetter,
                    this.bodyCellElement);

                // Escreve o xml
                var resultBuilder          = new StringBuilder();
                XmlWriterSettings settings = new XmlWriterSettings
                {
                    Indent             = this.indent,
                    IndentChars        = "  ",
                    NewLineChars       = "\r\n",
                    NewLineHandling    = NewLineHandling.Replace,
                    OmitXmlDeclaration = true
                };

                using (var writer = XmlWriter.Create(resultBuilder, settings))
                {
                    tableDocument.Save(writer);
                }

                return(resultBuilder.ToString());
            }
        }
        /// <summary>
        /// Obtém a table corresponde ao enumerável de objectos.
        /// </summary>
        /// <param name="enumerable">O enumerável.</param>
        /// <returns>A representação da tabela.</returns>
        public string GetHtmlTableFromEnumerable(IEnumerable <ObjType> enumerable)
        {
            if (enumerable == null)
            {
                throw new ArgumentNullException("enumerable");
            }
            else
            {
                var tableDocument   = new XmlDocument();
                var attributeSetter = new AttributeSetter();
                var elementSetter   = new ElementSetter();

                // Inicializa a tabela
                var table = tableDocument.CreateElement("table");
                attributeSetter.CurrentElement = table;
                this.tableAttributes.Invoke(attributeSetter);
                tableDocument.AppendChild(table);

                // Cabeçalho
                var tableHeader = tableDocument.CreateElement("thead");
                attributeSetter.CurrentElement = tableHeader;
                this.tableHeaderAttributes.Invoke(attributeSetter);
                table.AppendChild(tableHeader);

                // Linhas do cabeçalho
                var tableRow = tableDocument.CreateElement("tr");
                attributeSetter.CurrentElement = tableRow;
                this.headerRowAttributesSetter.Invoke(attributeSetter);
                tableHeader.AppendChild(tableRow);

                // Colunas do cabeçalho
                var fieldCount = this.columns.Count;
                for (int i = 0; i < fieldCount; ++i)
                {
                    var columnName = this.columns[i].Item1;
                    var tableCell  = tableDocument.CreateElement("th");
                    elementSetter.CurrentElement = tableCell;
                    this.headerCellElement.Invoke(i, columnName, elementSetter);
                    tableRow.AppendChild(tableCell);
                }

                // Corpo
                var tableBody = tableDocument.CreateElement("tbody");
                attributeSetter.CurrentElement = tableBody;
                this.tableBodyAttributes.Invoke(attributeSetter);
                table.AppendChild(tableBody);

                // Linhas da tabela
                var line       = 0;
                var enumerator = enumerable.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    var currentObj = enumerator.Current;

                    tableRow = tableDocument.CreateElement("tr");
                    attributeSetter.CurrentElement = tableRow;
                    this.bodyRowAttributesSetter.Invoke(line, attributeSetter);
                    tableBody.AppendChild(tableRow);

                    var length = this.columns.Count;
                    for (int i = 0; i < length; ++i)
                    {
                        var currentAction = this.columns[i].Item2;

                        var tableCell = tableDocument.CreateElement("td");
                        elementSetter.CurrentElement = tableCell;
                        currentAction.Invoke(line, i, currentObj, elementSetter);

                        tableRow.AppendChild(tableCell);
                    }

                    ++line;
                }

                // Escreve o xml
                var resultBuilder          = new StringBuilder();
                XmlWriterSettings settings = new XmlWriterSettings
                {
                    Indent             = this.indent,
                    IndentChars        = "  ",
                    NewLineChars       = "\r\n",
                    NewLineHandling    = NewLineHandling.Replace,
                    OmitXmlDeclaration = true
                };

                using (var writer = XmlWriter.Create(resultBuilder, settings))
                {
                    tableDocument.Save(writer);
                }

                return(resultBuilder.ToString());
            }
        }