ReadVisibility
    (
        ExcelTableReader.ExcelTableRow oRow,
        GroupVisibilityConverter oGroupVisibilityConverter,
        String sGroupName,
        HashSet<String> oSkippedGroupNames,
        HashSet<String> oHiddenGroupNames
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oGroupVisibilityConverter != null);
        Debug.Assert( !String.IsNullOrEmpty(sGroupName) );
        Debug.Assert(oSkippedGroupNames != null);
        Debug.Assert(oHiddenGroupNames != null);
        AssertValid();

        // Assume a default visibility.

        Visibility eVisibility = Visibility.Show;

        String sVisibility;

        if (
            oRow.TryGetNonEmptyStringFromCell(
                CommonTableColumnNames.Visibility, out sVisibility)
            &&
            !oGroupVisibilityConverter.TryWorkbookToGraph(
                sVisibility, out eVisibility)
            )
        {
            OnInvalidVisibility(oRow);
        }

        if (eVisibility == Visibility.Skip)
        {
            oSkippedGroupNames.Add(sGroupName);
        }
        else if (eVisibility == Visibility.Hide)
        {
            oHiddenGroupNames.Add(sGroupName);
        }
    }
    ReadGroupTable
    (
        ListObject oGroupTable,
        ReadWorkbookContext oReadWorkbookContext,
        HashSet<String> oSkippedGroupNames,
        HashSet<String> oHiddenGroupNames
    )
    {
        Debug.Assert(oGroupTable != null);
        Debug.Assert(oReadWorkbookContext != null);
        Debug.Assert(oSkippedGroupNames != null);
        Debug.Assert(oHiddenGroupNames != null);
        AssertValid();

        if (oReadWorkbookContext.FillIDColumns)
        {
            FillIDColumn(oGroupTable);
        }

        Dictionary<String, ExcelTemplateGroupInfo> oGroupNameDictionary =
            new Dictionary<String, ExcelTemplateGroupInfo>();

        ColorConverter2 oColorConverter2 =
            oReadWorkbookContext.ColorConverter2;

        GroupVisibilityConverter oGroupVisibilityConverter =
            new GroupVisibilityConverter();

        BooleanConverter oBooleanConverter =
            oReadWorkbookContext.BooleanConverter;

        ExcelTableReader oExcelTableReader = new ExcelTableReader(oGroupTable);

        foreach ( ExcelTableReader.ExcelTableRow oRow in
            oExcelTableReader.GetRows() )
        {
            // Get the group information.

            String sGroupName;
            Color oVertexColor;
            VertexShape eVertexShape;

            if (
                !oRow.TryGetNonEmptyStringFromCell(GroupTableColumnNames.Name,
                    out sGroupName)
                ||
                !TryGetColor(oRow, GroupTableColumnNames.VertexColor,
                    oColorConverter2, out oVertexColor)
                ||
                !TryGetVertexShape(oRow, GroupTableColumnNames.VertexShape,
                    out eVertexShape)
                )
            {
                continue;
            }

            ReadVisibility(oRow, oGroupVisibilityConverter, sGroupName,
                oSkippedGroupNames, oHiddenGroupNames);

            Boolean bCollapsed = false;
            Boolean bCollapsedCellValue;

            if (
                TryGetBoolean(oRow, GroupTableColumnNames.Collapsed,
                    oBooleanConverter, out bCollapsedCellValue)
                &&
                bCollapsedCellValue
                )
            {
                bCollapsed = true;
            }

            String sCollapsedAttributes;

            if ( !oRow.TryGetNonEmptyStringFromCell(
                GroupTableColumnNames.CollapsedAttributes,
                out sCollapsedAttributes) )
            {
                sCollapsedAttributes = null;
            }

            Int32 iRowIDAsInt32;
            Nullable<Int32> iRowID = null;

            if ( oRow.TryGetInt32FromCell(CommonTableColumnNames.ID,
                out iRowIDAsInt32) )
            {
                iRowID = iRowIDAsInt32;
            }

            ExcelTemplateGroupInfo oExcelTemplateGroupInfo =
                new ExcelTemplateGroupInfo(sGroupName, iRowID, oVertexColor,
                    eVertexShape, bCollapsed, sCollapsedAttributes);

            if (oReadWorkbookContext.ReadGroupLabels)
            {
                String sLabel;

                if ( oRow.TryGetNonEmptyStringFromCell(
                    GroupTableColumnNames.Label, out sLabel) )
                {
                    oExcelTemplateGroupInfo.Label = sLabel;
                }
            }

            if (!oReadWorkbookContext.IgnoreVertexLocations)
            {
                System.Drawing.PointF oCollapsedLocation;

                if ( TryGetLocation(oRow, GroupTableColumnNames.CollapsedX,
                    GroupTableColumnNames.CollapsedY,
                    oReadWorkbookContext.VertexLocationConverter,
                    out oCollapsedLocation) )
                {
                    oExcelTemplateGroupInfo.CollapsedLocation =
                        oCollapsedLocation;
                }
            }

            try
            {
                oGroupNameDictionary.Add(sGroupName, oExcelTemplateGroupInfo);
            }
            catch (ArgumentException)
            {
                Range oInvalidCell = oRow.GetRangeForCell(
                    GroupTableColumnNames.Name);

                OnWorkbookFormatError( String.Format(

                    "The cell {0} contains a duplicate group name.  There"
                    + " can't be two rows with the same group name."
                    ,
                    ExcelUtil.GetRangeAddress(oInvalidCell)
                    ),

                    oInvalidCell
                );
            }
        }

        return (oGroupNameDictionary);
    }