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);
    }
    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) );
    }
    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);
    }
    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() );
        }
    }
    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);
    }
    ReadCellAndSetMetadata
    (
        ExcelTableReader.ExcelTableRow oRow,
        String sColumnName,
        IMetadataProvider oEdgeOrVertex,
        String sKeyName
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert( !String.IsNullOrEmpty(sColumnName) );
        Debug.Assert(oEdgeOrVertex != null);
        Debug.Assert( !String.IsNullOrEmpty(sKeyName) );
        AssertValid();

        String sNonEmptyString;

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

        oEdgeOrVertex.SetValue(sKeyName, sNonEmptyString);

        return (true);
    }
    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);
        }
    }
    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) );
    }
    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);
    }
    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);
    }
    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);
            }
        }
    }
    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);
    }
    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);
    }
    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);
    }
    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) );
    }
    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;
        }
    }
    ReadAlpha
    (
        ExcelTableReader.ExcelTableRow oRow,
        IMetadataProvider oEdgeOrVertex
    )
    {
        Debug.Assert(oRow != null);
        Debug.Assert(oEdgeOrVertex != null);

        AssertValid();

        String sString;

        if ( !oRow.TryGetNonEmptyStringFromCell(CommonTableColumnNames.Alpha,
            out sString) )
        {
            return (false);
        }

        Single fAlpha;

        if ( !Single.TryParse(sString, out fAlpha) )
        {
            Range oInvalidCell = oRow.GetRangeForCell(
                CommonTableColumnNames.Alpha);

            OnWorkbookFormatError( String.Format(

                "The cell {0} contains an invalid opacity.  The opacity,"
                + " which is optional, must be a number.  Any number is"
                + " acceptable, although {1} (transparent) is used for any"
                + " number less than {1} and {2} (opaque) is used for any"
                + " number greater than {2}."
                ,
                ExcelUtil.GetRangeAddress(oInvalidCell),
                AlphaConverter.MinimumAlphaWorkbook,
                AlphaConverter.MaximumAlphaWorkbook
                ),

                oInvalidCell
            );
        }

        fAlpha = m_oAlphaConverter.WorkbookToGraph(fAlpha);

        oEdgeOrVertex.SetValue(ReservedMetadataKeys.PerAlpha, fAlpha);

        return (fAlpha == 0);
    }