Reads rows from an Excel table.
Use GetRows to iterate the rows of an Excel table. Filtered rows are automatically skipped, and the rows are buffered internally to eliminate having to make a slow COM interop call to get each row.

A collection of the column names in the table is available via the property.

Note that although this class is optimized for reading a table, the property allows you to write to the cells in a row as well. However, each such write operation incurs the overhead of a COM call, so this class should not be used when there are hundreds of cells that need to be written to.

Inheritance: Object
Example #1
0
            //*********************************************************************
            //  Constructor: ExcelTableRow()
            //
            /// <summary>
            /// Initializes a new instance of the <see cref="ExcelTableRow" />
            /// class.
            /// </summary>
            ///
            /// <param name="excelTableReader">
            /// The table reader object that owns this object.
            /// </param>
            //*********************************************************************

            public ExcelTableRow
            (
                ExcelTableReader excelTableReader
            )
            {
                m_oExcelTableReader = excelTableReader;

                AssertValid();
            }
    ReadPolarCoordinates
    (
        ExcelTableReader.ExcelTableRow oRow,
        IVertex oVertex
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oVertex != null);
        AssertValid();

        String sR;

        Boolean bHasR = oRow.TryGetNonEmptyStringFromCell(
            VertexTableColumnNames.PolarR, out sR);

        String sAngle;

        Boolean bHasAngle = oRow.TryGetNonEmptyStringFromCell(
            VertexTableColumnNames.PolarAngle, out sAngle);

        if (bHasR != bHasAngle)
        {
            // R or Angle alone won't do.

            goto Error;
        }

        if (!bHasR && !bHasAngle)
        {
            return (false);
        }

        Single fR, fAngle;

        if ( !Single.TryParse(sR, out fR) ||
            !Single.TryParse(sAngle, out fAngle) )
        {
            goto Error;
        }

        oVertex.SetValue(ReservedMetadataKeys.PolarLayoutCoordinates,
            new SinglePolarCoordinates(fR, fAngle) );

        return (true);

        Error:

            Range oInvalidCell = oRow.GetRangeForCell(
                VertexTableColumnNames.PolarR);

            OnWorkbookFormatError( String.Format(

                "There is a problem with the vertex polar coordinates at {0}."
                + " If you enter polar coordinates, they must include both"
                + " {1} and {2} numbers.  Any numbers are acceptable."
                + "\r\n\r\n"
                + "Polar coordinates are used only when a Layout of Polar"
                + " or Polar Absolute is selected in the graph pane."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell),
                VertexTableColumnNames.PolarR,
                VertexTableColumnNames.PolarAngle
                ),

                oInvalidCell
                );

            // Make the compiler happy.

            return (false);
    }
    ReadImageUri
    (
        ExcelTableReader.ExcelTableRow oRow,
        IVertex oVertex,
        VertexRadiusConverter oVertexRadiusConverter,
        Nullable<Single> oVertexImageSize
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oVertex != null);
        Debug.Assert(oVertexRadiusConverter != null);
        AssertValid();

        String sImageUri;

        if ( !oRow.TryGetNonEmptyStringFromCell(
            VertexTableColumnNames.ImageUri, out sImageUri) )
        {
            return (false);
        }

        if ( sImageUri.ToLower().StartsWith("www.") )
        {
            // The Uri class thinks that "www.somewhere.com" is a relative
            // path.  Fix that.

            sImageUri= "http://" + sImageUri;
        }

        Uri oUri;

        // Is the URI either an URL or a full file path?

        if ( !Uri.TryCreate(sImageUri, UriKind.Absolute, out oUri) )
        {
            // No.  It appears to be a relative path.

            Range oCell = oRow.GetRangeForCell(
                VertexTableColumnNames.ImageUri);

            String sWorkbookPath =
                ( (Workbook)(oCell.Worksheet.Parent) ).Path;

            if ( !String.IsNullOrEmpty(sWorkbookPath) )
            {
                sImageUri = Path.Combine(sWorkbookPath, sImageUri);
            }
            else
            {
                OnWorkbookFormatError( String.Format(

                    "The image file path specified in cell {0} is a relative"
                    + " path.  Relative paths must be relative to the saved"
                    + " workbook file, but the workbook hasn't been saved yet."
                    + "  Either save the workbook or change the image file to"
                    + " an absolute path, such as \"C:\\MyImages\\Image.jpg\"."
                    ,
                    ExcelUtil.GetRangeAddress(oCell)
                    ),

                    oCell
                    );
            }
        }

        // Note that sImageUri may or may not be a valid URI string.  If it is
        // not, GetImageSynchronousIgnoreDpi() will return an error image.

        ImageSource oImage =
            ( new WpfImageUtil() ).GetImageSynchronousIgnoreDpi(sImageUri);

        if (oVertexImageSize.HasValue)
        {
            // Resize the image.

            Double dLongerDimension =
                oVertexRadiusConverter.WorkbookToLongerImageDimension(
                    oVertexImageSize.Value);

            Debug.Assert(dLongerDimension >= 1);

            oImage = ( new WpfImageUtil() ).ResizeImage(oImage,
                (Int32)dLongerDimension);
        }

        oVertex.SetValue(ReservedMetadataKeys.PerVertexImage, oImage);

        return (true);
    }
    ReadRadius
    (
        ExcelTableReader.ExcelTableRow oRow,
        VertexRadiusConverter oVertexRadiusConverter,
        IVertex oVertex
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oVertex != null);
        Debug.Assert(oVertexRadiusConverter != null);
        AssertValid();

        String sRadius;

        if ( !oRow.TryGetNonEmptyStringFromCell(VertexTableColumnNames.Radius,
            out sRadius) )
        {
            return ( new Nullable<Single>() );
        }

        Single fRadius;

        if ( !Single.TryParse(sRadius, out fRadius) )
        {
            Range oInvalidCell = oRow.GetRangeForCell(
                VertexTableColumnNames.Radius);

            OnWorkbookFormatError( String.Format(

                "The cell {0} contains an invalid size.  The vertex size,"
                + " which is optional, must be a number.  Any number is"
                + " acceptable, although {1} is used for any number less than"
                + " {1} and {2} is used for any number greater than {2}."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell),
                VertexRadiusConverter.MinimumRadiusWorkbook,
                VertexRadiusConverter.MaximumRadiusWorkbook
                ),

                oInvalidCell
            );
        }

        oVertex.SetValue( ReservedMetadataKeys.PerVertexRadius,
            oVertexRadiusConverter.WorkbookToGraph(fRadius) );

        return ( new Nullable<Single>(fRadius) );
    }
    ReadVertexTable
    (
        ListObject oVertexTable,
        ReadWorkbookContext oReadWorkbookContext,
        IGraph oGraph,
        out Boolean bLayoutAndZOrderSet
    )
    {
        Debug.Assert(oVertexTable != null);
        Debug.Assert(oReadWorkbookContext != null);
        Debug.Assert(oGraph != null);
        AssertValid();

        bLayoutAndZOrderSet = false;

        if (GetTableColumnIndex(oVertexTable,
            VertexTableColumnNames.VertexName, false) == NoSuchColumn)
        {
            // Nothing can be done without vertex names.

            return;
        }

        Boolean bReadAllEdgeAndVertexColumns =
            oReadWorkbookContext.ReadAllEdgeAndVertexColumns;

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

        // Get the names of all the column pairs that are used to add custom
        // menu items to the vertex context menu in the graph.

        TableColumnAdder oTableColumnAdder = new TableColumnAdder();

        ICollection< KeyValuePair<String, String> > aoCustomMenuItemPairNames =
            oTableColumnAdder.GetColumnPairNames(oVertexTable,
                VertexTableColumnNames.CustomMenuItemTextBase,
                VertexTableColumnNames.CustomMenuItemActionBase);

        IVertexCollection oVertices = oGraph.Vertices;

        Dictionary<String, IVertex> oVertexNameDictionary =
            oReadWorkbookContext.VertexNameDictionary;

        Dictionary<Int32, IIdentityProvider> oEdgeRowIDDictionary =
            oReadWorkbookContext.EdgeRowIDDictionary;

        BooleanConverter oBooleanConverter =
            oReadWorkbookContext.BooleanConverter;

        VertexVisibilityConverter oVertexVisibilityConverter =
            new VertexVisibilityConverter();

        VertexLabelPositionConverter oVertexLabelPositionConverter =
            new VertexLabelPositionConverter();

        ExcelTableReader oExcelTableReader =
            new ExcelTableReader(oVertexTable);

        HashSet<String> oColumnNamesToExclude = new HashSet<String>(
            new String[] {
                VertexTableColumnNames.VertexName
                } );

        foreach ( ExcelTableReader.ExcelTableRow oRow in
            oExcelTableReader.GetRows() )
        {
            // Get the name of the vertex.

            String sVertexName;

            if ( !oRow.TryGetNonEmptyStringFromCell(
                VertexTableColumnNames.VertexName, out sVertexName) )
            {
                continue;
            }

            // If the vertex was added to the graph as part of an edge,
            // retrieve the vertex.

            IVertex oVertex;

            if ( !oVertexNameDictionary.TryGetValue(sVertexName, out oVertex) )
            {
                oVertex = null;
            }

            // Assume a default visibility.

            Visibility eVisibility = Visibility.ShowIfInAnEdge;
            String sVisibility;

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

            switch (eVisibility)
            {
                case Visibility.ShowIfInAnEdge:

                    // If the vertex is part of an edge, show it using the
                    // specified vertex attributes.  Otherwise, skip the vertex
                    // row.

                    if (oVertex == null)
                    {
                        continue;
                    }

                    break;

                case Visibility.Skip:

                    // Skip the vertex row and any edge rows that include the
                    // vertex.  Do not read them into the graph.

                    if (oVertex != null)
                    {
                        // Remove the vertex and its incident edges from the
                        // graph and dictionaries.

                        RemoveVertex(oVertex, oReadWorkbookContext, oGraph);
                    }

                    continue;

                case Visibility.Hide:

                    // If the vertex is part of an edge, hide it and its
                    // incident edges.  Otherwise, skip the vertex row.

                    if (oVertex == null)
                    {
                        continue;
                    }

                    HideVertex(oVertex);

                    break;

                case Visibility.Show:

                    // Show the vertex using the specified attributes
                    // regardless of whether it is part of an edge.

                    if (oVertex == null)
                    {
                        oVertex = CreateVertex(sVertexName, oVertices,
                            oVertexNameDictionary);
                    }

                    oVertex.SetValue(
                        ReservedMetadataKeys.VertexHasVisibilityOfShow, null);

                    break;

                default:

                    Debug.Assert(false);
                    break;
            }

            Debug.Assert(oVertex != null);

            // If ReadWorkbookContext.FillIDColumns is true, add the vertex to
            // the vertex row ID dictionary and set the vertex's Tag to the row
            // ID.

            oReadWorkbookContext.AddToRowIDDictionary(oRow, oVertex, false);

            if (bReadAllEdgeAndVertexColumns)
            {
                // All columns except the vertex name should be read and stored
                // as metadata on the vertex.

                ReadAllColumns( oExcelTableReader, oRow, oVertex,
                    oColumnNamesToExclude);

                continue;
            }

            // Layout and z-order.

            if ( ReadLayoutAndZOrder(oRow, oVertex) )
            {
                bLayoutAndZOrderSet = true;
            }

            // Location and Locked.

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

                Boolean bLocationSpecified = TryGetLocation(oRow,
                    VertexTableColumnNames.X, VertexTableColumnNames.Y,
                    oReadWorkbookContext.VertexLocationConverter,
                    out oLocation);

                if (bLocationSpecified)
                {
                    oVertex.Location = oLocation;
                }

                ReadLocked(oRow, oBooleanConverter, bLocationSpecified,
                    oVertex);
            }

            // Polar coordinates.

            ReadPolarCoordinates(oRow, oVertex);

            // Marked.

            ReadMarked(oRow, oBooleanConverter, oVertex);

            // Custom menu items.

            if (aoCustomMenuItemPairNames.Count > 0)
            {
                ReadCustomMenuItems(oRow, aoCustomMenuItemPairNames, oVertex);
            }

            // Alpha.

            ReadAlpha(oRow, oVertex);

            // Tooltip.

            ReadCellAndSetMetadata(oRow, VertexTableColumnNames.ToolTip,
                oVertex, ReservedMetadataKeys.PerVertexToolTip);

            // Label.

            if (oReadWorkbookContext.ReadVertexLabels)
            {
                ReadCellAndSetMetadata(oRow, VertexTableColumnNames.Label,
                    oVertex, ReservedMetadataKeys.PerVertexLabel);
            }

            // Label fill color.

            ReadColor(oRow, VertexTableColumnNames.LabelFillColor, oVertex,
                ReservedMetadataKeys.PerVertexLabelFillColor,
                oReadWorkbookContext.ColorConverter2);

            // Label position.

            ReadLabelPosition(oRow, oVertexLabelPositionConverter, oVertex);

            // Radius.

            Nullable<Single> oRadiusWorkbook = new Nullable<Single>();

            oRadiusWorkbook = ReadRadius(oRow,
                oReadWorkbookContext.VertexRadiusConverter, oVertex);

            // Shape.

            VertexShape eVertexShape;

            if ( !ReadShape(oRow, oVertex, out eVertexShape) )
            {
                eVertexShape = oReadWorkbookContext.DefaultVertexShape;
            }

            // Label font size.

            if (eVertexShape == VertexShape.Label && oRadiusWorkbook.HasValue)
            {
                // The vertex radius is used to specify font size when the
                // shape is Label.

                oVertex.SetValue( ReservedMetadataKeys.PerVertexLabelFontSize,
                    oReadWorkbookContext.VertexRadiusConverter.
                        WorkbookToLabelFontSize(oRadiusWorkbook.Value) );
            }

            // Image URI.

            if (eVertexShape == VertexShape.Image &&
                oReadWorkbookContext.ReadVertexImages)
            {
                ReadImageUri(oRow, oVertex,
                    oReadWorkbookContext.VertexRadiusConverter,

                    oRadiusWorkbook.HasValue ? oRadiusWorkbook :
                        oReadWorkbookContext.DefaultVertexImageSize
                    );
            }

            // Color

            ReadColor(oRow, VertexTableColumnNames.Color, oVertex,
                ReservedMetadataKeys.PerColor,
                oReadWorkbookContext.ColorConverter2);
        }

        if (bReadAllEdgeAndVertexColumns)
        {
            // Store the vertex column names on the graph.

            oGraph.SetValue( ReservedMetadataKeys.AllVertexMetadataKeys,
                FilterColumnNames(oExcelTableReader, oColumnNamesToExclude) );
        }
    }
    ReadMarked
    (
        ExcelTableReader.ExcelTableRow oRow,
        BooleanConverter oBooleanConverter,
        IVertex oVertex
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oBooleanConverter != null);
        Debug.Assert(oVertex != null);
        AssertValid();

        Boolean bMarked;

        if ( !TryGetBoolean(oRow, VertexTableColumnNames.IsMarked,
            oBooleanConverter, out bMarked) )
        {
            return;
        }

        oVertex.SetValue(ReservedMetadataKeys.Marked, bMarked);

        return;
    }
    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);
    }
    ReadTopColumn
    (
        ListObject oTopMetricsTable,
        String sColumnHeader,
        StringBuilder oTopMetrics
    )
    {
        Debug.Assert(oTopMetricsTable != null);
        Debug.Assert( !String.IsNullOrEmpty(sColumnHeader) );
        Debug.Assert(oTopMetrics != null);

        StringBuilder oTopColumn = new StringBuilder();
        Boolean bColumnIsEmpty = true;

        oTopColumn.Append(sColumnHeader);
        oTopColumn.Append(':');

        ExcelTableReader oExcelTableReader =
            new ExcelTableReader(oTopMetricsTable);

        foreach ( ExcelTableReader.ExcelTableRow oRow in
            oExcelTableReader.GetRows() )
        {
            String sItemName;

            if ( oRow.TryGetNonEmptyStringFromCell(sColumnHeader,
                out sItemName) )
            {
                StringUtil.AppendAfterEmptyLine(oTopColumn, sItemName);
                bColumnIsEmpty = false;
            }
        }

        if (!bColumnIsEmpty)
        {
            StringUtil.AppendSectionSeparator(oTopMetrics);
            oTopMetrics.Append( oTopColumn.ToString() );
        }
    }
    PrependGroupLabelsWithGroupNames
    (
        ListObject oGroupTable
    )
    {
        Debug.Assert(oGroupTable != null);

        // Use ExcelTableReader to accomplish the task with minimal code.
        //
        // Note that ExcelTableReader is optimized for reading, and that its
        // use for writing incurs the overhead of a COM call for each written
        // cell.  There typically aren't many groups, though, so this is
        // probably tolerable.
        //
        // If this turns out to be too slow, something similar to the code in
        // TableColumnMapper.MapViaCopy() will need to be implemented.

        ExcelTableReader oExcelTableReader = new ExcelTableReader(oGroupTable);

        if (
            oExcelTableReader.ColumnNames.Contains(GroupTableColumnNames.Name)
            &&
            oExcelTableReader.ColumnNames.Contains(GroupTableColumnNames.Label)
            )
        {
            foreach (ExcelTableReader.ExcelTableRow oRow in
                oExcelTableReader.GetRows() )
            {
                String sName;

                if ( oRow.TryGetNonEmptyStringFromCell(
                    GroupTableColumnNames.Name, out sName) )
                {
                    String sLabel;

                    if ( oRow.TryGetNonEmptyStringFromCell(
                        GroupTableColumnNames.Label, out sLabel) )
                    {
                        sName += ": " + sLabel;
                    }

                    oRow.GetRangeForCell(GroupTableColumnNames.Label)
                        .set_Value(Missing.Value, sName);
                }
            }
        }
    }
    FilterColumnNames
    (
        ExcelTableReader oExcelTableReader,
        HashSet<String> oColumnNamesToExclude
    )
    {
        Debug.Assert(oExcelTableReader != null);
        Debug.Assert(oColumnNamesToExclude != null);
        AssertValid();

        List<String> oFilteredColumnNames = new List<String>();

        foreach (String sColumnName in oExcelTableReader.ColumnNames)
        {
            if ( !oColumnNamesToExclude.Contains(sColumnName) )
            {
                oFilteredColumnNames.Add(sColumnName);
            }
        }

        return ( oFilteredColumnNames.ToArray() );
    }
    ReadAllColumns
    (
        ExcelTableReader oExcelTableReader,
        ExcelTableReader.ExcelTableRow oRow,
        IMetadataProvider oEdgeOrVertex,
        HashSet<String> oColumnNamesToExclude
    )
    {
        Debug.Assert(oExcelTableReader != null);
        Debug.Assert(oRow != null);
        Debug.Assert(oEdgeOrVertex != null);
        Debug.Assert(oColumnNamesToExclude != null);
        AssertValid();

        foreach (String sColumnName in oExcelTableReader.ColumnNames)
        {
            String sValue;

            if ( !oColumnNamesToExclude.Contains(sColumnName) &&
                oRow.TryGetNonEmptyStringFromCell(sColumnName, out sValue) )
            {
                oEdgeOrVertex.SetValue(sColumnName, sValue);
            }
        }
    }
    TryGetVertexShape
    (
        ExcelTableReader.ExcelTableRow oRow,
        String sColumnName,
        out VertexShape eShape
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert( !String.IsNullOrEmpty(sColumnName) );
        AssertValid();

        eShape = VertexShape.Circle;
        String sShape;

        if ( !oRow.TryGetNonEmptyStringFromCell(sColumnName, out sShape) )
        {
            return (false);
        }

        VertexShapeConverter oVertexShapeConverter =
            new VertexShapeConverter();

        if ( !oVertexShapeConverter.TryWorkbookToGraph(sShape, out eShape) )
        {
            OnWorkbookFormatErrorWithDropDown(oRow, sColumnName, "shape");
        }

        return (true);
    }
    TryGetLocation
    (
        ExcelTableReader.ExcelTableRow oRow,
        String sXColumnName,
        String sYColumnName,
        VertexLocationConverter oVertexLocationConverter,
        out PointF oLocation
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert( !String.IsNullOrEmpty(sXColumnName) );
        Debug.Assert( !String.IsNullOrEmpty(sYColumnName) );
        Debug.Assert(oVertexLocationConverter != null);
        AssertValid();

        oLocation = PointF.Empty;

        String sX, sY;

        Boolean bHasX = oRow.TryGetNonEmptyStringFromCell(
            sXColumnName, out sX);

        Boolean bHasY = oRow.TryGetNonEmptyStringFromCell(
            sYColumnName, out sY);

        if (bHasX != bHasY)
        {
            // X or Y alone won't do.

            goto Error;
        }

        if (!bHasX && !bHasY)
        {
            return (false);
        }

        Single fX, fY;

        if ( !Single.TryParse(sX, out fX) || !Single.TryParse(sY, out fY) )
        {
            goto Error;
        }

        // Transform the location from workbook coordinates to graph
        // coordinates.

        oLocation = oVertexLocationConverter.WorkbookToGraph(fX, fY);

        return (true);

        Error:

            Range oInvalidCell = oRow.GetRangeForCell(sXColumnName);

            OnWorkbookFormatError( String.Format(

                "There is a problem with the location at {0}.  If you enter a"
                + " location, it must include both X and Y numbers.  Any"
                + " numbers are acceptable, although {1} is used for any"
                + " number less than {1} and and {2} is used for any number"
                + " greater than {2}."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell),

                VertexLocationConverter.MinimumXYWorkbook.ToString(
                    ExcelTemplateForm.Int32Format),

                VertexLocationConverter.MaximumXYWorkbook.ToString(
                    ExcelTemplateForm.Int32Format)
                ),

                oInvalidCell
                );

            // Make the compiler happy.

            return (false);
    }
    TryGetColor
    (
        ExcelTableReader.ExcelTableRow oRow,
        String sColumnName,
        ColorConverter2 oColorConverter2,
        out Color oColor
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert( !String.IsNullOrEmpty(sColumnName) );
        Debug.Assert(oColorConverter2 != null);
        AssertValid();

        oColor = Color.Empty;

        String sColor;

        if ( !oRow.TryGetNonEmptyStringFromCell(sColumnName, out sColor) )
        {
            return (false);
        }

        if ( !oColorConverter2.TryWorkbookToGraph(sColor, out oColor) )
        {
            Range oInvalidCell = oRow.GetRangeForCell(sColumnName);

            OnWorkbookFormatError( String.Format(

                "The cell {0} contains an unrecognized color.  Right-click the"
                + " cell and select Select Color on the right-click menu."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell)
                ),

                oInvalidCell
            );
        }

        return (true);
    }
    GetRowIDDictionary
    (
        ListObject oTable,
        String sColumnName
    )
    {
        Debug.Assert(oTable != null);
        Debug.Assert( !String.IsNullOrEmpty(sColumnName) );

        Dictionary<Int32, Object> oRowIDDictionary =
            new Dictionary<Int32, Object>();

        // The code that reads the table can handle hidden rows, but not hidden
        // columns.  Temporarily show all hidden columns in the table.

        ExcelHiddenColumns oHiddenColumns =
            ExcelColumnHider.ShowHiddenColumns(oTable);

        try
        {
            ExcelTableReader oExcelTableReader = new ExcelTableReader(oTable);

            foreach ( ExcelTableReader.ExcelTableRow oRow in
                oExcelTableReader.GetRows() )
            {
                Int32 iRowID;
                Double dCellValue;

                if (
                    oRow.TryGetInt32FromCell(CommonTableColumnNames.ID,
                        out iRowID)
                    &&
                    oRow.TryGetDoubleFromCell(sColumnName, out dCellValue)
                    )
                {
                    oRowIDDictionary[iRowID] = dCellValue;
                }
            }
        }
        finally
        {
            ExcelColumnHider.RestoreHiddenColumns(oTable, oHiddenColumns);
        }

        return (oRowIDDictionary);
    }
        //*********************************************************************
        //  Constructor: ExcelTableRow()
        //
        /// <summary>
        /// Initializes a new instance of the <see cref="ExcelTableRow" />
        /// class.
        /// </summary>
        ///
        /// <param name="excelTableReader">
        /// The table reader object that owns this object.
        /// </param>
        //*********************************************************************

        public ExcelTableRow
        (
            ExcelTableReader excelTableReader
        )
        {
            m_oExcelTableReader = excelTableReader;

            AssertValid();
        }
    ReadGroupVertexTable
    (
        ListObject oGroupVertexTable,
        ReadWorkbookContext oReadWorkbookContext,
        Dictionary<String, ExcelTemplateGroupInfo> oGroupNameDictionary,
        IGraph oGraph
    )
    {
        Debug.Assert(oGroupVertexTable != null);
        Debug.Assert(oReadWorkbookContext != null);
        Debug.Assert(oGroupNameDictionary != null);
        Debug.Assert(oGraph != null);
        AssertValid();

        Dictionary<String, IVertex> oVertexNameDictionary =
            oReadWorkbookContext.VertexNameDictionary;

        ExcelTableReader oExcelTableReader =
            new ExcelTableReader(oGroupVertexTable);

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

            String sGroupName, sVertexName;

            if (
                !oRow.TryGetNonEmptyStringFromCell(
                    GroupVertexTableColumnNames.GroupName, out sGroupName)
                ||
                !oRow.TryGetNonEmptyStringFromCell(
                    GroupVertexTableColumnNames.VertexName, out sVertexName)
                )
            {
                continue;
            }

            // Get the group information for the vertex.

            ExcelTemplateGroupInfo oExcelTemplateGroupInfo;
            IVertex oVertex;

            if (
                !oGroupNameDictionary.TryGetValue(sGroupName,
                    out oExcelTemplateGroupInfo)
                ||
                !oVertexNameDictionary.TryGetValue(sVertexName,
                    out oVertex)
                )
            {
                continue;
            }

            // If the vertex should get its color or shape from the group, set
            // the vertex's color or shape.

            Boolean bReadColorFromGroup, bReadShapeFromGroup;

            GetReadColorAndShapeFlags(oVertex, oExcelTemplateGroupInfo,
                oReadWorkbookContext, out bReadColorFromGroup,
                out bReadShapeFromGroup);

            if (bReadColorFromGroup)
            {
                oVertex.SetValue(ReservedMetadataKeys.PerColor,
                    oExcelTemplateGroupInfo.VertexColor);
            }

            if (bReadShapeFromGroup)
            {
                oVertex.SetValue(ReservedMetadataKeys.PerVertexShape,
                    oExcelTemplateGroupInfo.VertexShape);
            }

            oExcelTemplateGroupInfo.Vertices.AddLast(oVertex);
        }
    }
    ReadEdgeTable
    (
        ListObject oEdgeTable,
        ReadWorkbookContext oReadWorkbookContext,
        IGraph oGraph
    )
    {
        Debug.Assert(oEdgeTable != null);
        Debug.Assert(oReadWorkbookContext != null);
        Debug.Assert(oGraph != null);
        AssertValid();

        Boolean bReadAllEdgeAndVertexColumns =
            oReadWorkbookContext.ReadAllEdgeAndVertexColumns;

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

        Dictionary<String, IVertex> oVertexNameDictionary =
            oReadWorkbookContext.VertexNameDictionary;

        EdgeVisibilityConverter oEdgeVisibilityConverter =
            new EdgeVisibilityConverter();

        Boolean bGraphIsDirected =
            (oGraph.Directedness == GraphDirectedness.Directed);

        ExcelTableReader oExcelTableReader = new ExcelTableReader(oEdgeTable);
        IVertexCollection oVertices = oGraph.Vertices;
        IEdgeCollection oEdges = oGraph.Edges;

        HashSet<String> oColumnNamesToExclude = new HashSet<String>(
            new String[] {
                EdgeTableColumnNames.Vertex1Name,
                EdgeTableColumnNames.Vertex2Name
                } );

        foreach ( ExcelTableReader.ExcelTableRow oRow in
            oExcelTableReader.GetRows() )
        {
            // Get the names of the edge's vertices.

            String sVertex1Name, sVertex2Name;

            Boolean bVertex1IsEmpty = !oRow.TryGetNonEmptyStringFromCell(
                EdgeTableColumnNames.Vertex1Name, out sVertex1Name);

            Boolean bVertex2IsEmpty = !oRow.TryGetNonEmptyStringFromCell(
                EdgeTableColumnNames.Vertex2Name, out sVertex2Name);

            if (bVertex1IsEmpty && bVertex2IsEmpty)
            {
                // Skip empty rows.

                continue;
            }

            if (bVertex1IsEmpty || bVertex2IsEmpty)
            {
                // A half-empty row is an error.

                OnHalfEmptyEdgeRow(oRow, bVertex1IsEmpty);
            }

            // Assume a default visibility.

            Visibility eVisibility = Visibility.Show;

            String sVisibility;

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

            if (eVisibility == Visibility.Skip)
            {
                // Skip the edge an continue to the next edge.

                continue;
            }

            // Create the specified vertices or retrieve them from the
            // dictionary.

            IVertex oVertex1 = VertexNameToVertex(
                sVertex1Name, oVertices, oVertexNameDictionary);

            IVertex oVertex2 = VertexNameToVertex(
                sVertex2Name, oVertices, oVertexNameDictionary);

            // Add an edge connecting the vertices.

            IEdge oEdge = oEdges.Add(oVertex1, oVertex2, bGraphIsDirected);

            // If ReadWorkbookContext.FillIDColumns is true, add the edge to
            // the edge row ID dictionary and set the edge's Tag to the row ID.

            oReadWorkbookContext.AddToRowIDDictionary(oRow, oEdge, true);

            if (bReadAllEdgeAndVertexColumns)
            {
                // All columns except the vertex names should be read and
                // stored as metadata on the edge.

                ReadAllColumns(oExcelTableReader, oRow, oEdge,
                    oColumnNamesToExclude);

                continue;
            }

            if (eVisibility == Visibility.Hide)
            {
                // Hide the edge and continue to the next edge.

                oEdge.SetValue(ReservedMetadataKeys.Visibility,
                    VisibilityKeyValue.Hidden);

                continue;
            }

            // Alpha.

            Boolean bAlphaIsZero = ReadAlpha(oRow, oEdge);

            if (bAlphaIsZero)
            {
                continue;
            }

            // Color.

            ReadColor(oRow, EdgeTableColumnNames.Color, oEdge,
                ReservedMetadataKeys.PerColor,
                oReadWorkbookContext.ColorConverter2);

            // Width.

            ReadWidth(oRow, oReadWorkbookContext.EdgeWidthConverter, oEdge);

            // Style.

            ReadStyle(oRow, oReadWorkbookContext.EdgeStyleConverter, oEdge);

            // Label.

            if (oReadWorkbookContext.ReadEdgeLabels)
            {
                ReadCellAndSetMetadata(oRow, EdgeTableColumnNames.Label, oEdge,
                    ReservedMetadataKeys.PerEdgeLabel);

                ReadColor(oRow, EdgeTableColumnNames.LabelTextColor, oEdge,
                    ReservedMetadataKeys.PerEdgeLabelTextColor,
                    oReadWorkbookContext.ColorConverter2);

                ReadLabelFontSize(oRow, oReadWorkbookContext.FontSizeConverter,
                    oEdge);
            }

            // Weight.

            if (oReadWorkbookContext.ReadEdgeWeights)
            {
                ReadEdgeWeight(oRow, oEdge);
            }
        }

        if (bReadAllEdgeAndVertexColumns)
        {
            // Store the edge column names on the graph.

            oGraph.SetValue( ReservedMetadataKeys.AllEdgeMetadataKeys,
                FilterColumnNames(oExcelTableReader, oColumnNamesToExclude) );
        }
    }
    ReadLocked
    (
        ExcelTableReader.ExcelTableRow oRow,
        BooleanConverter oBooleanConverter,
        Boolean bLocationSpecified,
        IVertex oVertex
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oBooleanConverter != null);
        Debug.Assert(oVertex != null);
        AssertValid();

        Boolean bLocked;

        if ( !TryGetBoolean(oRow, VertexTableColumnNames.Locked,
            oBooleanConverter, out bLocked) )
        {
            return;
        }

        if (bLocked && !bLocationSpecified)
        {
            Range oInvalidCell = oRow.GetRangeForCell(
                VertexTableColumnNames.Locked);

            OnWorkbookFormatError( String.Format(

                "The cell {0} indicates that the vertex should be locked,"
                + " but the vertex has no X and Y location values.  Either"
                + " clear the lock or specify a vertex location."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell)
                ),

                oInvalidCell
            );
        }

        oVertex.SetValue(ReservedMetadataKeys.LockVertexLocation, bLocked);
    }
    ReadWidth
    (
        ExcelTableReader.ExcelTableRow oRow,
        EdgeWidthConverter oEdgeWidthConverter,
        IEdge oEdge
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oEdgeWidthConverter != null);
        AssertValid();
                
        String sWidth;

        if ( !oRow.TryGetNonEmptyStringFromCell(EdgeTableColumnNames.Width,
            out sWidth) )
        {
            return;
        }

        Single fWidth;

        if ( !Single.TryParse(sWidth, out fWidth) )
        {
            Range oInvalidCell = oRow.GetRangeForCell(
                EdgeTableColumnNames.Width);

            OnWorkbookFormatError( String.Format(

                "The cell {0} contains an invalid width.  The edge width,"
                + " which is optional, must be a number.  Any number is"
                + " acceptable, although {1} is used for any number less than"
                + " {1} and {2} is used for any number greater than {2}."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell),
                EdgeWidthConverter.MinimumWidthWorkbook,
                EdgeWidthConverter.MaximumWidthWorkbook
                ),

                oInvalidCell
            );
        }

        oEdge.SetValue(ReservedMetadataKeys.PerEdgeWidth, 
            oEdgeWidthConverter.WorkbookToGraph(fWidth) );
    }
    ReadCustomMenuItems
    (
        ExcelTableReader.ExcelTableRow oRow,
        ICollection< KeyValuePair<String, String> > aoCustomMenuItemPairNames,
        IVertex oVertex
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(aoCustomMenuItemPairNames != null);
        Debug.Assert(oVertex != null);
        AssertValid();

        // List of string pairs, one pair for each custom menu item to add to
        // the vertex's context menu in the graph.  The key is the custom menu
        // item text and the value is the custom menu item action.

        List<KeyValuePair<String, String>> oCustomMenuItemInformation =
            new List<KeyValuePair<String, String>>();

        foreach (KeyValuePair<String, String> oPairNames in
            aoCustomMenuItemPairNames)
        {
            String sCustomMenuItemText, sCustomMenuItemAction;

            // Both the menu item text and menu item action must be specified. 
            // Skip the pair if either is missing.

            if (
                !oRow.TryGetNonEmptyStringFromCell(oPairNames.Key,
                    out sCustomMenuItemText)
                ||
                !oRow.TryGetNonEmptyStringFromCell(oPairNames.Value,
                    out sCustomMenuItemAction)
                )
            {
                continue;
            }

            Int32 iCustomMenuItemTextLength = sCustomMenuItemText.Length;

            if (iCustomMenuItemTextLength > MaximumCustomMenuItemTextLength)
            {
                Range oInvalidCell = oRow.GetRangeForCell(oPairNames.Key);

                OnWorkbookFormatError( String.Format(

                    "The cell {0} contains custom menu item text that is {1}"
                    + " characters long.  Custom menu item text can't be"
                    + " longer than {2} characters."
                    ,
                    ExcelUtil.GetRangeAddress(oInvalidCell),
                    iCustomMenuItemTextLength,
                    MaximumCustomMenuItemTextLength
                    ),

                    oInvalidCell
                    );
            }

            oCustomMenuItemInformation.Add( new KeyValuePair<String, String>(
                sCustomMenuItemText, sCustomMenuItemAction) );
        }

        if (oCustomMenuItemInformation.Count > 0)
        {
            oVertex.SetValue( ReservedMetadataKeys.CustomContextMenuItems,
                oCustomMenuItemInformation.ToArray() );
        }
    }
    ReadStyle
    (
        ExcelTableReader.ExcelTableRow oRow,
        EdgeStyleConverter oEdgeStyleConverter,
        IEdge oEdge
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oEdgeStyleConverter != null);
        AssertValid();

        String sStyle;

        if ( !oRow.TryGetNonEmptyStringFromCell(EdgeTableColumnNames.Style,
            out sStyle) )
        {
            return;
        }

        EdgeStyle eStyle;

        if ( !oEdgeStyleConverter.TryWorkbookToGraph(sStyle, out eStyle) )
        {
            OnWorkbookFormatErrorWithDropDown(oRow, EdgeTableColumnNames.Style,
                "style");
        }

        oEdge.SetValue(ReservedMetadataKeys.PerEdgeStyle, eStyle);
    }
    ReadLabelPosition
    (
        ExcelTableReader.ExcelTableRow oRow,
        VertexLabelPositionConverter oVertexLabelPositionConverter,
        IVertex oVertex
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oVertex != null);
        Debug.Assert(oVertexLabelPositionConverter != null);
        AssertValid();

        String sLabelPosition;

        if ( !oRow.TryGetNonEmptyStringFromCell(
            VertexTableColumnNames.LabelPosition, out sLabelPosition) )
        {
            return;
        }

        VertexLabelPosition eLabelPosition;

        if ( !oVertexLabelPositionConverter.TryWorkbookToGraph(sLabelPosition,
            out eLabelPosition) )
        {
            OnWorkbookFormatErrorWithDropDown(oRow,
                VertexTableColumnNames.LabelPosition, "label position");
        }

        oVertex.SetValue( ReservedMetadataKeys.PerVertexLabelPosition,
            eLabelPosition);
    }
    ReadEdgeWeight
    (
        ExcelTableReader.ExcelTableRow oRow,
        IEdge oEdge
    )
    {
        Debug.Assert(oRow != null);
        AssertValid();

        Double dEdgeWeight = 0;

        if ( !oRow.TryGetDoubleFromCell(EdgeTableColumnNames.EdgeWeight,
            out dEdgeWeight)
            )
        {
            // There is no edge weight column, or the edge weight cell for this
            // edge is empty.

            dEdgeWeight = 1;
        }

        oEdge.SetValue(ReservedMetadataKeys.EdgeWeight, dEdgeWeight);
    }
    ReadShape
    (
        ExcelTableReader.ExcelTableRow oRow,
        IVertex oVertex,
        out VertexShape eShape
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oVertex != null);
        AssertValid();

        if ( TryGetVertexShape(oRow, VertexTableColumnNames.Shape,
            out eShape) )
        {
            oVertex.SetValue(ReservedMetadataKeys.PerVertexShape, eShape);
            return (true);
        }

        return (false);
    }
    ReadLabelFontSize
    (
        ExcelTableReader.ExcelTableRow oRow,
        FontSizeConverter oFontSizeConverter,
        IEdge oEdge
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oFontSizeConverter != null);
        AssertValid();

        String sLabelFontSize;

        if ( !oRow.TryGetNonEmptyStringFromCell(
            EdgeTableColumnNames.LabelFontSize, out sLabelFontSize) )
        {
            return;
        }

        Single fLabelFontSize;

        if ( !Single.TryParse(sLabelFontSize, out fLabelFontSize) )
        {
            Range oInvalidCell = oRow.GetRangeForCell(
                EdgeTableColumnNames.LabelFontSize);

            OnWorkbookFormatError( String.Format(

                "The cell {0} contains an invalid label font size.  The label"
                + " font size, which is optional, must be a number.  Any"
                + " number is acceptable, although {1} is used for any number"
                + " less than {1} and {2} is used for any number greater than"
                + " {2}."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell),
                FontSizeConverter.MinimumFontSizeWorkbook,
                FontSizeConverter.MaximumFontSizeWorkbook
                ),

                oInvalidCell
            );
        }

        oEdge.SetValue(ReservedMetadataKeys.PerEdgeLabelFontSize, 
            oFontSizeConverter.WorkbookToGraph(fLabelFontSize) );
    }
    ReadLayoutAndZOrder
    (
        ExcelTableReader.ExcelTableRow oRow,
        IVertex oVertex
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oVertex != null);
        AssertValid();

        String sOrder;

        if ( !oRow.TryGetNonEmptyStringFromCell(
            VertexTableColumnNames.LayoutOrder, out sOrder) )
        {
            return (false);
        }

        Single fOrder;

        if ( !Single.TryParse(sOrder, out fOrder) )
        {
            Range oInvalidCell = oRow.GetRangeForCell(
                VertexTableColumnNames.LayoutOrder);

            OnWorkbookFormatError( String.Format(

                "The cell {0} contains an invalid layout order.  The layout"
                + " order, which is optional, must be a number."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell)
                ),

                oInvalidCell
            );
        }

        oVertex.SetValue( ReservedMetadataKeys.SortableLayoutAndZOrder,
            fOrder);

        return (true);
    }
    OnHalfEmptyEdgeRow
    (
        ExcelTableReader.ExcelTableRow oRow,
        Boolean bVertex1IsEmpty
    )
    {
        Debug.Assert(oRow != null);

        AssertValid();

        Range oVertex1Cell = oRow.GetRangeForCell(
            EdgeTableColumnNames.Vertex1Name);

        Range oVertex2Cell = oRow.GetRangeForCell(
            EdgeTableColumnNames.Vertex2Name);

        Range oEmptyCell = bVertex1IsEmpty ? oVertex1Cell : oVertex2Cell;
        Range oNonEmptyCell = bVertex1IsEmpty ? oVertex2Cell : oVertex1Cell;

        String sEmptyRangeAddress = ExcelUtil.GetRangeAddress(oEmptyCell);
        String sNonEmptyRangeAddress = ExcelUtil.GetRangeAddress(oNonEmptyCell);

        String sErrorMessage = String.Format(

            "Cell {0} contains a vertex name but cell {1} is empty."
            + "  You can include an empty row, which will be ignored,"
            + " but you can't include a half-empty row."
            + "\r\n\r\n"
            + "You can fix the problem by entering a vertex name in {1} or"
            + " deleting the name in {0}."
            ,
            sNonEmptyRangeAddress,
            sEmptyRangeAddress
            );

        OnWorkbookFormatError(sErrorMessage, oEmptyCell);
    }
    AddToRowIDDictionary
    (
        ExcelTableReader.ExcelTableRow row,
        IIdentityProvider edgeOrVertex,
        Boolean isEdge
    )
    {
        Debug.Assert(row != null);
        Debug.Assert(edgeOrVertex != null);
        AssertValid();

        if (!m_bFillIDColumns)
        {
            return;
        }

        Dictionary<Int32, IIdentityProvider> oRowIDDictionary = isEdge ?
            m_oEdgeRowIDDictionary : m_oVertexRowIDDictionary;

        // Because the derived class fills in its ID column if the column
        // exists, each cell in the column should be valid.

        String sRowID;
        Int32 iRowID = Int32.MinValue;

        if (
            row.TryGetNonEmptyStringFromCell(CommonTableColumnNames.ID,
                out sRowID)
            &&
            Int32.TryParse(sRowID, out iRowID)
            )
        {
            oRowIDDictionary.Add(iRowID, edgeOrVertex);

            Debug.Assert(edgeOrVertex is IMetadataProvider);

            // Store the row ID in the edge or vertex tag.

            ( (IMetadataProvider)edgeOrVertex ).Tag = iRowID;
        }
    }
    TryGetBoolean
    (
        ExcelTableReader.ExcelTableRow oRow,
        String sColumnName,
        BooleanConverter oBooleanConverter,
        out Boolean bBoolean
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert( !String.IsNullOrEmpty(sColumnName) );
        Debug.Assert(oBooleanConverter != null);
        AssertValid();

        bBoolean = false;
        String sBoolean;

        if ( !oRow.TryGetNonEmptyStringFromCell(sColumnName, out sBoolean) )
        {
            return (false);
        }

        if ( !oBooleanConverter.TryWorkbookToGraph(sBoolean, out bBoolean) )
        {
            OnWorkbookFormatErrorWithDropDown(oRow, sColumnName, "value");
        }

        return (true);
    }