Subversion Repositories DevTools

Rev

Rev 2090 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using Word;

namespace EA_DocGen
{


   /// <summary>
   /// Summary description for createWordDoc.
   /// </summary>
   public class createWordDoc : System.Windows.Forms.Form
   {
      // Enterprise Architect Model related data
      private EA.Repository EA_Repository = null;
      private EA.Package EA_ParentPackage = null;
      private EA.Project EA_Project;
      
      private EA_Utilities EA_Utils = null;

      private bool oneShot_skipRootPackage = false;

      // MS-Office Word related data
      private Word.Application WordApp = null;
      private Word.Document WordDocument = null;
      private Word.Range WordRange;
      private object startLocation;
      private object endLocation;
      
      private int[] DocSection = null;
      private int   iDocSection = 0;

      private System.Windows.Forms.TextBox textBox_template;
      private System.Windows.Forms.Button button_browse_template;
      private System.Windows.Forms.TextBox textBox_output_file;
      private System.Windows.Forms.Button button_browse_output_file;
      private System.Windows.Forms.Button button_cancel;
      private System.Windows.Forms.Button button_generate;
      private System.Windows.Forms.Button button_view_output;
      private System.Windows.Forms.Label label1;
      private System.Windows.Forms.Label label2;
      private System.Windows.Forms.TextBox textBox_progress;
      private System.Windows.Forms.Label label3;
      private System.Windows.Forms.CheckBox checkBox_WordVisibility;
      /// <summary>
      /// Required designer variable.
      /// </summary>
      private System.ComponentModel.Container components = null;

      public createWordDoc(EA.Repository theRepository, EA.Package theParentPackage, EA_Utilities theEA_Utils)
      {
         //
         // Required for Windows Form Designer support
         //
         InitializeComponent();

         EA_Repository    = theRepository;
         EA_ParentPackage = theParentPackage;
         EA_Project       = EA_Repository.GetProjectInterface();

         EA_Utils         = theEA_Utils;

         DocSection = new int[12];
         for (int i=0; i<DocSection.Length; i++)
         {
            DocSection[i] = 0;
         }

         WordApp = new Word.Application();
      }

      /// <summary>
      /// Clean up any resources being used.
      /// </summary>
      protected override void Dispose( bool disposing )
      {
         if( disposing )
         {
            if(components != null)
            {
               components.Dispose();
            }
         }
         base.Dispose( disposing );
      }

      
 

      #region Windows Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent()
      {
         this.textBox_template = new System.Windows.Forms.TextBox();
         this.button_browse_template = new System.Windows.Forms.Button();
         this.textBox_output_file = new System.Windows.Forms.TextBox();
         this.button_browse_output_file = new System.Windows.Forms.Button();
         this.button_cancel = new System.Windows.Forms.Button();
         this.button_generate = new System.Windows.Forms.Button();
         this.button_view_output = new System.Windows.Forms.Button();
         this.label1 = new System.Windows.Forms.Label();
         this.label2 = new System.Windows.Forms.Label();
         this.textBox_progress = new System.Windows.Forms.TextBox();
         this.label3 = new System.Windows.Forms.Label();
         this.checkBox_WordVisibility = new System.Windows.Forms.CheckBox();
         this.SuspendLayout();
         // 
         // textBox_template
         // 
         this.textBox_template.Location = new System.Drawing.Point(16, 40);
         this.textBox_template.Name = "textBox_template";
         this.textBox_template.Size = new System.Drawing.Size(592, 22);
         this.textBox_template.TabIndex = 0;
         this.textBox_template.Text = "";
         this.textBox_template.TextChanged += new System.EventHandler(this.textBox_template_TextChanged);
         // 
         // button_browse_template
         // 
         this.button_browse_template.Location = new System.Drawing.Point(616, 40);
         this.button_browse_template.Name = "button_browse_template";
         this.button_browse_template.Size = new System.Drawing.Size(80, 23);
         this.button_browse_template.TabIndex = 1;
         this.button_browse_template.Text = "Browse";
         this.button_browse_template.Click += new System.EventHandler(this.button_browse_template_Click);
         // 
         // textBox_output_file
         // 
         this.textBox_output_file.Location = new System.Drawing.Point(16, 112);
         this.textBox_output_file.Name = "textBox_output_file";
         this.textBox_output_file.Size = new System.Drawing.Size(592, 22);
         this.textBox_output_file.TabIndex = 2;
         this.textBox_output_file.Text = "";
         this.textBox_output_file.TextChanged += new System.EventHandler(this.textBox_output_file_TextChanged);
         // 
         // button_browse_output_file
         // 
         this.button_browse_output_file.Location = new System.Drawing.Point(616, 112);
         this.button_browse_output_file.Name = "button_browse_output_file";
         this.button_browse_output_file.Size = new System.Drawing.Size(80, 24);
         this.button_browse_output_file.TabIndex = 3;
         this.button_browse_output_file.Text = "Browse";
         this.button_browse_output_file.Click += new System.EventHandler(this.button_browse_output_file_Click);
         // 
         // button_cancel
         // 
         this.button_cancel.Location = new System.Drawing.Point(616, 384);
         this.button_cancel.Name = "button_cancel";
         this.button_cancel.Size = new System.Drawing.Size(80, 23);
         this.button_cancel.TabIndex = 4;
         this.button_cancel.Text = "Close";
         this.button_cancel.Click += new System.EventHandler(this.button_cancel_Click);
         // 
         // button_generate
         // 
         this.button_generate.Location = new System.Drawing.Point(496, 384);
         this.button_generate.Name = "button_generate";
         this.button_generate.Size = new System.Drawing.Size(96, 24);
         this.button_generate.TabIndex = 5;
         this.button_generate.Text = "Generate";
         this.button_generate.Click += new System.EventHandler(this.button_generate_Click);
         // 
         // button_view_output
         // 
         this.button_view_output.Location = new System.Drawing.Point(376, 384);
         this.button_view_output.Name = "button_view_output";
         this.button_view_output.Size = new System.Drawing.Size(96, 23);
         this.button_view_output.TabIndex = 6;
         this.button_view_output.Text = "View Output";
         this.button_view_output.Click += new System.EventHandler(this.button_view_output_Click);
         // 
         // label1
         // 
         this.label1.Location = new System.Drawing.Point(16, 16);
         this.label1.Name = "label1";
         this.label1.Size = new System.Drawing.Size(224, 23);
         this.label1.TabIndex = 7;
         this.label1.Text = "Input Template or Document Name";
         // 
         // label2
         // 
         this.label2.Location = new System.Drawing.Point(16, 88);
         this.label2.Name = "label2";
         this.label2.Size = new System.Drawing.Size(280, 23);
         this.label2.TabIndex = 8;
         this.label2.Text = "Output Document Name";
         // 
         // textBox_progress
         // 
         this.textBox_progress.Location = new System.Drawing.Point(16, 184);
         this.textBox_progress.Multiline = true;
         this.textBox_progress.Name = "textBox_progress";
         this.textBox_progress.ReadOnly = true;
         this.textBox_progress.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
         this.textBox_progress.Size = new System.Drawing.Size(680, 168);
         this.textBox_progress.TabIndex = 9;
         this.textBox_progress.Text = "";
         // 
         // label3
         // 
         this.label3.Location = new System.Drawing.Point(16, 152);
         this.label3.Name = "label3";
         this.label3.TabIndex = 10;
         this.label3.Text = "Progress";
         // 
         // checkBox_WordVisibility
         // 
         this.checkBox_WordVisibility.Location = new System.Drawing.Point(16, 384);
         this.checkBox_WordVisibility.Name = "checkBox_WordVisibility";
         this.checkBox_WordVisibility.Size = new System.Drawing.Size(256, 24);
         this.checkBox_WordVisibility.TabIndex = 17;
         this.checkBox_WordVisibility.Text = "Show MS-WORD During Construction";
         // 
         // createWordDoc
         // 
         this.AutoScaleBaseSize = new System.Drawing.Size(6, 15);
         this.ClientSize = new System.Drawing.Size(712, 432);
         this.Controls.Add(this.checkBox_WordVisibility);
         this.Controls.Add(this.label3);
         this.Controls.Add(this.textBox_progress);
         this.Controls.Add(this.textBox_output_file);
         this.Controls.Add(this.textBox_template);
         this.Controls.Add(this.label2);
         this.Controls.Add(this.label1);
         this.Controls.Add(this.button_view_output);
         this.Controls.Add(this.button_generate);
         this.Controls.Add(this.button_cancel);
         this.Controls.Add(this.button_browse_output_file);
         this.Controls.Add(this.button_browse_template);
         this.Name = "createWordDoc";
         this.Text = "createWordDoc";
         this.Closing += new System.ComponentModel.CancelEventHandler(this.createWordDoc_Closing);
         this.ResumeLayout(false);

      }
      #endregion


      #region Message handlers
      private void button_view_output_Click(object sender, System.EventArgs e)
      {
         if (WordApp != null)
         {
            WordApp.Visible = true;
         }
      }



 
      private void button_generate_Click(object sender, System.EventArgs e)
      {
         if (this.textBox_template.Text.Length > 0)
         {
            if (this.textBox_output_file.Text.Length > 0)
            {
               createTheWordDoc();
            }
            else
            {
               MessageBox.Show("Error - must specify an output file");
            }
         }
         else
         {
            MessageBox.Show("Error - must specify an input template file");
         }
      }

      private void button_cancel_Click(object sender, System.EventArgs e)
      {
         this.Close();   
      }

      private void button_browse_template_Click(object sender, System.EventArgs e)
      {
         OpenFileDialog dialog = new OpenFileDialog();
         dialog.Filter = "Word Files (*.doc;*.dot)|*.doc;*.dot|All files(*.*)|*.*";
         dialog.FilterIndex = 1;
         dialog.ShowDialog();
         this.textBox_template.Text = dialog.FileName;

      }

      private void button_browse_output_file_Click(object sender, System.EventArgs e)
      {
         OpenFileDialog dialog = new OpenFileDialog();
         dialog.Filter = "Word Files (*.doc;*.dot)|*.doc;*.dot|All files(*.*)|*.*";
         dialog.FilterIndex = 1;
         dialog.CheckFileExists = false;
         dialog.ShowDialog();
         this.textBox_output_file.Text = dialog.FileName;
      }

      private void textBox_template_TextChanged(object sender, System.EventArgs e)
      {
      
      }

      private void textBox_output_file_TextChanged(object sender, System.EventArgs e)
      {
      
      }
      
      private void createWordDoc_Closing(object sender, System.ComponentModel.CancelEventArgs e)
      {
         if (WordApp != null)
         {
            // try-catch just in case user closed the application themselves whilst viewing the
            // generated output document
            try 
            {
               object nothing = Type.Missing;
               object notTrue = false;

               WordApp.Application.Quit( ref notTrue, ref nothing, ref nothing);
            }
            catch (Exception createWordDoc_Closing_exception)
            {
               // dummy statement
               String s = createWordDoc_Closing_exception.Message;
            }
         }
      }

      #endregion


      #region word document generation code

      /// <summary>
      /// This class is one derived from the EA_UtilitiesRecursionWorker base class so that
      /// an instance of it can be used as a parameter in the EA_Utilities.findAndProcessPackageElements
      /// method which recursively parses a given package. Thus, clients can parse EA model structure but
      /// specify their own functionality to be carried out on packages and elements found in the 
      /// parsing.
      /// This particular derived class is designed to collect a list of element names from the
      /// package, from elements that have a type that is acceptable.
      /// </summary>
      public class ElementAccumulator : EA_UtilitiesRecursionWorker
      {
         public ArrayList Elements = null;

         private ArrayList validElementTypes = null;

         public ElementAccumulator(ArrayList elementTypeList): base()
         {
            validElementTypes = elementTypeList;
            Elements = new ArrayList();
         }

         public override void processElement( EA.Element theElement )
         {
            if (validElementTypes.Contains( theElement.Type ))
            {
               if ( (theElement.Type == "Requirement") && (theElement.Status == "Rejected") )
                  return;

               Elements.Add( theElement );
            }
         }
      }


      private void trackDocSection(int recurse_level)
      {
         iDocSection = recurse_level;
         DocSection[iDocSection]++;     
         for (int i=iDocSection+1; i<DocSection.Length; i++)
            DocSection[i] = 0;
      }


      /// <summary>
      /// Displays progress of the document generation in the createWordDocument dialog
      /// </summary>
      /// <param name="prefix"></param>
      /// <param name="EA_string"></param>
      private void displayProgress(string prefix, string EA_string)
      {
         textBox_progress.AppendText( prefix + EA_string + "\r\n" );

         // Display to the output tab as well - as a longer term record of what happened
         string s = "";
         for (int i = 1; i<=iDocSection; i++)
         {
            if (i!=1)
               s = s + ".";
            s = s + DocSection[i].ToString();
         }
         while (s.Length < 30)
            s += " ";

         EA_Repository.WriteOutput( Main.GUI_OUTPUT_TAB_NAME, s + prefix + EA_string, 0);
      }


      /// <summary>
      /// This method takes the specified EA diagram element and puts the diagram image
      /// into the clipboard from where it can be pasted into the word document. After
      /// pasting it into the document, it sets the style and other aspects of the image.
      /// </summary>
      /// <param name="theDiagram"></param>
      private void appendAndSelectDiagramViaClipboard(EA.Diagram theDiagram)
      {
         // open the diagram in EA and copy it's image to the clipboard
         if (EA_Project.PutDiagramImageOnClipboard(theDiagram.DiagramGUID,0))
         {
            // create a range at the end of the document
            startLocation = WordDocument.Content.End;
            WordDocument.Content.InsertParagraphAfter();
            endLocation = WordDocument.Content.End;
            WordRange = WordDocument.Range(ref startLocation, ref endLocation);
            object direction = Word.WdCollapseDirection.wdCollapseEnd;
            WordRange.Collapse(ref direction);  // collapse prevents existing content being replaced

            // Get ready for the diagram paste and the formatting we want to do on it with the
            // selection object.
            SelectInsertionPointAtEndOfDocument();

            // Paste the diagram into the document
            WordRange.Paste();           

            // Set style of the diagram to "Normal" so that it can occupy space all the way to the left margin
            object l_style = "Normal";
            endLocation = WordDocument.Content.End;
            WordRange = WordDocument.Range(ref startLocation, ref endLocation);
            WordRange.set_Style(ref l_style);
            
            // Center the diagram on the page
            object unit = Word.WdUnits.wdLine;
            object missing = Type.Missing;
            object count = 1;
            WordApp.Selection.MoveDown( ref unit, ref count, ref missing);
            WordApp.Selection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;

            // Add a caption
            WordDocument.Content.InsertParagraphAfter();
            SelectInsertionPointAtEndOfDocument();         
            object Label = "Figure";
            object Title = Type.Missing;
            object TitleAutoText = Type.Missing;
            object Position = Word.WdCaptionPosition.wdCaptionPositionAbove;
            object ExcludeLabel = 0;
            WordApp.Selection.InsertCaption( ref Label, ref Title, ref TitleAutoText, ref Position, ref ExcludeLabel);
            WordApp.Selection.TypeText( ": " + theDiagram.Name.ToString());
            WordApp.Selection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
 
            // experimental code to create a bookmark for the figure caption
            //WordDocument.Paragraphs[ WordDocument.Paragraphs.Count ].Range.Select();
            //object lastParaRange = WordDocument.Paragraphs[ WordDocument.Paragraphs.Count ].Range;
            //string bookmarkName = theDiagram.Name;
            //bookmarkName = bookmarkName.Replace(' ', '_');
            //WordDocument.Bookmarks.Add( bookmarkName, ref lastParaRange );

            // Close the diagram in EA
            EA_Repository.CloseDiagram(theDiagram.DiagramID);
         }
      }


      /// <summary>
      /// Appends a specified text string to the word document, selects the new text, and applies
      /// the specified style formatting to it.
      /// </summary>
      /// <param name="wordText"></param>
      /// <param name="styleText"></param>
      /// <param name="continuation"></param>
      private void appendAndSelectText(string wordText, string styleText) 
      { 
         appendAndSelectText(wordText, styleText, false );
      }

      private void appendAndSelectText(string wordText, string styleText, bool continuation )
      {
         if (wordText.Length > 0)
         {
            object style = styleText;
            int i;
            startLocation = 0;
            endLocation = i = WordDocument.Content.End;

            WordRange = WordDocument.Range(ref startLocation, ref endLocation);

            if (!continuation)
               WordRange.InsertAfter( "\n" );
            
            WordRange.InsertAfter( wordText );

            // Make a range out of the pasted text
            startLocation = i;
            endLocation = WordDocument.Content.End;
            WordRange = WordDocument.Range(ref startLocation, ref endLocation);

            // and set the pasted text style
            WordRange.set_Style(ref style);
         }
      }
   

      /// <summary>
      /// Appends a specified text string to the word document, selects the new text, and applies
      /// a heading level style to it. 
      /// </summary>
      /// <param name="wordText"></param>
      /// <param name="level"></param>
      private void appendAndSelectHeadingText(string wordText, int level)
      {
         // Convert level to heading style
         String styleText;
         switch(level)
         {
            case 1: styleText = "Heading 1"; break;
            case 2: styleText = "Heading 2"; break;
            case 3: styleText = "Heading 3"; break;
            case 4: styleText = "Heading 4"; break;
            case 5: styleText = "Heading 5"; break;
            case 6: styleText = "Heading 6"; break;
            case 7: styleText = "Heading 7"; break;
            case 8: styleText = "Heading 8"; break;
            case 9: styleText = "Heading 9"; break;
            default: styleText = "Body 1"; break;
         }
         // append the text as a heading
         appendAndSelectText(wordText, styleText);
      }
 

      /// <summary>
      /// Appends a specified text string to the word document, selects the new text, and applies
      /// a num para heading level style to it. 
      /// </summary>
      /// <param name="wordText"></param>
      /// <param name="level"></param>
      private void appendAndSelectNumParaText(string wordText, int level)
      {
         // Convert level to heading style
         String styleText;
         switch(level)
         {
            case 1: styleText = "NumPara 1"; break;
            case 2: styleText = "NumPara 2"; break;
            case 3: styleText = "NumPara 3"; break;
            case 4: styleText = "NumPara 4"; break;
            case 5: styleText = "NumPara 5"; break;
            case 6: styleText = "NumPara 6"; break;
            case 7: styleText = "NumPara 7"; break;
            case 8: styleText = "NumPara 8"; break;
            case 9: styleText = "NumPara 9"; break;
            default: styleText = "Body 1"; break;
         }
         // append the text as a heading
         appendAndSelectText(wordText, styleText);
      }


      private void SelectInsertionPointAtEndOfDocument()
      {
         object unit; 
         object extend;

         unit = Word.WdUnits.wdStory; 
         extend = Word.WdMovementType.wdMove;
         WordApp.Selection.EndKey(ref unit, ref extend);
      }


      /// <summary>
      /// This is an attempt to create a table similar in style to those made by the ERG doc template macros.
      /// It is not exaclty that same though, and further work could be done to align it more precisely.
      /// </summary>
      /// <param name="tableTitle"></param>
      /// <param name="numRows"></param>
      /// <param name="numCols"></param>
      /// <returns></returns>
      private int createTable(string tableTitle, int numRows, int numCols)
      {
         WordDocument.Content.InsertParagraphAfter();
         SelectInsertionPointAtEndOfDocument();
         
         object Label = "Table";
         object Title = Type.Missing;
         object TitleAutoText = Type.Missing;
         object Position = Word.WdCaptionPosition.wdCaptionPositionAbove;
         object ExcludeLabel = 0;
         WordApp.Selection.InsertCaption( ref Label, ref Title, ref TitleAutoText, ref Position, ref ExcludeLabel);
         WordApp.Selection.TypeText( ": " + tableTitle);
         WordApp.Selection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
        
         startLocation = WordDocument.Content.End;
         WordDocument.Content.InsertParagraphAfter();
         endLocation = WordDocument.Content.End;
         WordRange = WordDocument.Range(ref startLocation, ref endLocation);

         object defaultTableBehaviour = Type.Missing;
         object autofitBehaviour = Type.Missing;
         Word.Table Table = WordDocument.Tables.Add( WordRange, numRows, numCols, ref defaultTableBehaviour, ref autofitBehaviour );

         Table.Rows[1].Shading.BackgroundPatternColor = Word.WdColor.wdColorGray10;
         Table.Borders.OutsideLineStyle = Word.WdLineStyle.wdLineStyleSingle;
         Table.Borders.InsideLineStyle = Word.WdLineStyle.wdLineStyleSingle;

         Table.Select();
         object tableTextStyle = "Table Text";
         Table.Range.set_Style( ref tableTextStyle );

         return WordDocument.Tables.Count;
      }

     

      /// <summary>
      /// This function process a package link element, parsing each linked package by the
      /// parse_package() recursive function.
      /// </summary>
      /// <param name="theElement"></param>
      /// <param name="recurse_level"></param>
      private void processPackageLink(EA.Element theElement, int recurse_level)
      {
         string [] EA_DocGenPkgLnk = null;
         string delimStr = "\r\n";
         char [] delim = delimStr.ToCharArray();
         EA_DocGenPkgLnk = theElement.Notes.ToString().Split(delim,100);
         string linkedName = "";

         foreach(string s in EA_DocGenPkgLnk)
         {
            if (s.Length > 0 && s != "\n" && s != "\r")
            {
               if (s == "skiproot")
               {
                  oneShot_skipRootPackage = true;
               }
               else if (s[0] == '{')
               {
                  EA.Package theFoundPackage = EA_Utils.EA_Finder.findPackageInRepositoryByGUID(s);
                  if (theFoundPackage != null)
                  {
                     parse_package(theFoundPackage, recurse_level);
                  }
                  else
                  {
                     MessageBox.Show("WARNING - Could not find linked package : " + linkedName );
                  }
               }
               else
               {
                  linkedName = s;
               }            
            }
         }
      }


      /// <summary>
      /// This function process a diagram link element, using the appendAndSelectDiagramViaClipboard()
      /// for each linked diagram.
      /// </summary>
      /// <param name="theElement"></param>
      private void processDiagramLink(EA.Element theElement)
      {
         string [] EA_DocGenDiagLnk = null;
         string delimStr = "\r\n";
         char [] delim = delimStr.ToCharArray();
         EA_DocGenDiagLnk = theElement.Notes.ToString().Split(delim,100);
         string linkedName = "";
         
         foreach(string s in EA_DocGenDiagLnk)
         {
            if (s.Length > 0 && s != "\n" && s != "\r" )
            {
               if (s[0] == '{')
               {
                  EA.Diagram theFoundDiagram = EA_Utils.EA_Finder.findDiagramInRepositoryByGUID(s);
                  if (theFoundDiagram != null)
                  {
                     displayProgress( "DIAGRAM: ", theFoundDiagram.Name.ToString() );
                     appendAndSelectDiagramViaClipboard( theFoundDiagram );
                  }
                  else 
                  {
                     MessageBox.Show("WARNING - Could not find linked diagram : " + linkedName );
                  }
               }
               else
               {
                  linkedName = s;
               }            
            }
         }         
      }


      /// <summary>
      /// This function process an element link element, parsing each linked element by the
      /// parse_element() recursive function.
      /// </summary>
      /// <param name="theElement"></param>
      /// <param name="recurse_level"></param>
      private void processElementLink(EA.Element theElement, int recurse_level )
      {
         string [] EA_DocGenEleLnk = null;
         string delimStr = "\r\n";
         char [] delim = delimStr.ToCharArray();
         EA_DocGenEleLnk = theElement.Notes.ToString().Split(delim,100);
         string linkedName = "";
         
         foreach(string s in EA_DocGenEleLnk)
         {
            if (s.Length > 0 && s != "\n" && s != "\r")
            {
               if ( s[0] == '{')
               {
                  EA.Element theFoundElement = EA_Utils.EA_Finder.findElementInRepositoryByGUID(s);
                  if (theFoundElement != null)
                  {
                     parse_element( theFoundElement, recurse_level );
                  }
                  else 
                  {
                     MessageBox.Show("WARNING - Could not find linked element : " + linkedName );
                  }
               }
               else
               {
                  linkedName = s;
               }            
            }
         }         
      }


      /// <summary>
      /// This method creates a table from the content of an EA_DocGenTable element in the model.
      /// The elements notes section contains the table parameters and cell content.
      /// See the EA_Utilities::AddTableElement() method for the layout of this content.
      /// </summary>
      /// <param name="theElement"></param>
      /// <param name="recurse_level"></param>
      private void processTableElement(EA.Element theElement, int recurse_level )
      {
         string [] EA_DocGenTable = null;
         string [] cells = null;

         string delimStr = "\r\n";
         char [] delim = delimStr.ToCharArray();

         string trimStr = " ";
         char [] trimChars = trimStr.ToCharArray();

         EA_DocGenTable = theElement.Notes.ToString().Split(delim,200);

         int columnCount = 0;
         int rowCount = 0;
         char seperator = ',';
         string tableTitle = "";
         bool gotSeperator = false;
         bool gotTitle = false;
        
         // Scan the notes content line by line looking for table parameters and counting
         // the number of table rows (which is not specified as an explicit table parameter).
         int s_ElementsConsumedByTableParams = 0;
         foreach(string s in EA_DocGenTable)
         {
            if (s.Length > 0 && s != "\n" && s != "\r" )
            {
               if (gotTitle == false && s.StartsWith("title"))
               {
                  s_ElementsConsumedByTableParams++;
                  tableTitle = EA_Utils.options.getOptionValue(s, tableTitle);
                  gotTitle = true;
                  if (tableTitle == "")
                  {
                     MessageBox.Show( "Table Element Serialisation Failed - Bad Title" );
                     break;
                  }
               }
            
               else if (columnCount == 0 && s.StartsWith("columns"))
               {
                  s_ElementsConsumedByTableParams++;
                  columnCount = EA_Utils.options.getOptionValue(s, columnCount);
                  if (columnCount == 0)
                  {
                     MessageBox.Show( "Table Element Serialisation Failed - bad column count" );
                     break;
                  }
               }
               else if (gotSeperator == false && s.StartsWith("seperator"))
               {
                  s_ElementsConsumedByTableParams++;
                  seperator = EA_Utils.options.getOptionValue(s, seperator);
                  gotSeperator = true;
               }
               else
               {
                  rowCount++;
               }
            }
         }

         if (gotTitle == true && gotSeperator == true && columnCount != 0)
         {
            if (rowCount < 2)
            {
               MessageBox.Show( "Table Element Serialisation Failed - Insufficient Rows" );
            }
            else
            {
               // create the table in the word doc
               int tableNum = createTable( tableTitle, rowCount, columnCount );
               Word.Table table = WordDocument.Tables[tableNum];
               object center = Word.WdParagraphAlignment.wdAlignParagraphCenter;

               // scan the element notes again to extract the cell content and add it to the
               // table we just created.
               int row = 1;
               int col = 1;
               foreach(string s in EA_DocGenTable)
               {
                  if (s.Length > 0 && s != "\n" && s != "\r" )
                  {
                     if (s_ElementsConsumedByTableParams > 0)
                     {
                        s_ElementsConsumedByTableParams--;
                     }
                     else
                     {
                        delimStr = seperator.ToString();
                        delim = delimStr.ToCharArray();
                        cells = s.Split(delim,columnCount);

                        if (cells.Length != columnCount)
                        {
                           MessageBox.Show( "Table Element Serialisation Failed - Bad Row" );
                           break;
                        }
                        else
                        {
                           // serialise table row
                           for(col=1; col<=columnCount; col++)
                           {
                              cells[col-1].TrimStart( trimChars );
                              cells[col-1].TrimEnd( trimChars );

                              table.Cell(row,col).Range.Text = cells[col-1];

                              // special handling for heading row
                              if (row == 1)
                              {
                                 table.Cell(row,col).Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
                                 table.Cell(row,col).Range.Font.Bold = 1;
                              }
                           }
                           row++;
                        }                     
                     }
                  }
               }
            }
         }
         else
         {
            MessageBox.Show( "Table Element Serialisation Failed - Table Parameters Incomplete" );
         }
      }


      /// <summary>
      /// This function process a text element. It is a simple wrapper for the 
      /// appendAndSelectText()function.
      /// </summary>
      /// <param name="theElement"></param>
      /// <param name="recurse_level"></param>
      private void processTextElement(EA.Element theElement, int recurse_level )
      {
         appendAndSelectText( theElement.Notes.ToString(), "Body 1" );
      }


 

      /// <summary>
      /// This function processes elements as if they represented document references for
      /// the References section of a document.
      /// </summary>
      /// <param name="thePackage"></param>
      private void createReferencesSection(EA.Package thePackage)
      {
         foreach(EA.Element theElement in thePackage.Elements)
         {
            // Here we want to apply the special ERG template styles for each reference
            // item.
            appendAndSelectText( theElement.Name.ToString(), "Reference List Number" );
            appendAndSelectText( theElement.Notes.ToString(), "Reference List Text" );
         }
      }


      private void createTerminologySection(EA.Package thePackage)
      {
         // Use local package elements as source of glossary, but if none are present, resort to using
         // the project glossary in the repository 
         int numItems = thePackage.Elements.Count;
         if (numItems == 0)
         {
            numItems = EA_Repository.Terms.Count;
         }

         if (numItems > 0)
         {
            // Create a table
            int tableNum = createTable("Terminology", numItems+1, 2);
            Word.Table table = WordDocument.Tables[tableNum];
            object center = Word.WdParagraphAlignment.wdAlignParagraphCenter;
            table.Cell(1,1).Range.Text = "Term";
            table.Cell(1,1).Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
            table.Cell(1,1).Range.Font.Bold = 1;
            table.Cell(1,2).Range.Text = "Definition";
            table.Cell(1,2).Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
            table.Cell(1,2).Range.Font.Bold = 1;
            table.Columns[1].SetWidth(100, Word.WdRulerStyle.wdAdjustSameWidth );
            int row = 2;

            if (thePackage.Elements.Count > 0)
            {
               // The advantage of using the package content is that the elements come out alphabetically
               // sorted already (assuming user has not over-riden the sort order in the project browser).
               // Also, the glossary content can be exactly tailored to what the document requires.
               foreach(EA.Element theElement in thePackage.Elements)
               {
                  table.Cell(row,1).Range.Text = theElement.Name.ToString();
                  table.Cell(row,2).Range.Text = theElement.Notes.ToString();
                  row++;
               }
            }
            else // default to using the project glossary in the repository
            {
               // TODO 
               // 1. Alphabetically Sort the glossary terms before adding them to the table
               //    The Repository glossary seems to be provided in non-sorted order unfortunately.

               // inject the glossary into the table
               foreach(EA.Term theTerm in EA_Repository.Terms)
               {
                  table.Cell(row,1).Range.Text = theTerm.Term;
                  table.Cell(row,2).Range.Text = theTerm.Meaning;
                  row++;
               }
            }
         }
      }


      private bool generatePackageHeadingAndDescription( EA.Package thePackage, int recurse_level )
      {
         // disarm the one-shot package skipping feature if it was armed, and exit the function.
         if (oneShot_skipRootPackage == true)
         {
            oneShot_skipRootPackage = false;
            return false;
         }

         // The package name is a heading, and the package notes are paragraphs 
         // directly under that heading
         displayProgress( "PACKAGE: ", thePackage.Name.ToString() );

         appendAndSelectHeadingText( thePackage.Name.ToString(), recurse_level );

         // Special handling for package called "Terminology" 
         if (thePackage.Name == "Terminology")
         {
            appendAndSelectText( thePackage.Notes.ToString(), "Body 1" );
            createTerminologySection(thePackage);
            return true;
         }

         if (thePackage.Name == "References")
         {
            appendAndSelectText( thePackage.Notes.ToString(), "Body 1" );
            createReferencesSection(thePackage);
            return true;
         }
         
         // use the package notes as body text and indicate to caller that package elements 
         // have not been consumed
         appendAndSelectText( thePackage.Notes.ToString(), "Body 1" );      
         return false;
      }

  
      private void generatePackageDiagrams( EA.Package thePackage )
      {
         // default handling of diagrams
         foreach(EA.Diagram theDiagram in thePackage.Diagrams)
         {
            displayProgress( "DIAGRAM: ", theDiagram.Name.ToString() );

            appendAndSelectDiagramViaClipboard( theDiagram );
         }
      }


      private void generateElementContent( EA.Element theElement, int recurse_level )
      {
         recurse_level++;
         if (recurse_level > 9)
         {
            throw new System.StackOverflowException("Max Recursion Depth (9) Exceeded" );
         }
         trackDocSection(recurse_level);
         
         if (EA_Utils.options.elementTypeFoundInEA_DocGen( theElement.Type ))
         {
            displayProgress( "ELEMENT: ", theElement.Name.ToString() );

            if ( theElement.Type.StartsWith("Requirement")
               && EA_Utils.options.opt_DisplayRequirementsWithStatus == true )
            {
               generateRequirementText( theElement );
            }
            else
            {
               if (theElement.Type.StartsWith("Requirement")
                  && EA_Utils.options.opt_DisplayRequirementElementsAsSections == false )
               {
                  string s;
                  s = EA_Utils.options.opt_RequirementElementDisplayFormat;
                  s = s.Replace(@"%s",@"{0}");
                  appendAndSelectText( String.Format( s, theElement.Name ), "Body 1");
                  WordRange.Font.Bold = 1;
               }
               else if ( EA_Utils.options.opt_ElementHeadingTransitionLevel > 0
                  && EA_Utils.options.opt_ElementHeadingTransitionLevel <= recurse_level )
                  appendAndSelectNumParaText( theElement.Name.ToString(), recurse_level );
               else
                  appendAndSelectHeadingText( theElement.Name.ToString(), recurse_level );
 
               appendAndSelectText( theElement.Notes.ToString(), "Body 1" );
            }
         }
         else
         {
            displayProgress( "FILTERED: ", theElement.Type );

         }
         // TODO
         // We will probably have to put special code in here at some point for attributes
         // of, and collections of things within elements. This will become apparent once
         // someone tries to produce a design document where such things need to be included
         // in the generated document. Lots of work needed here eventually.
         
      }


      /// <summary>
      /// This function was the only way I could figure out how to obtain a style object for
      /// the style name given. Everything else I tried, didn't work, even code from Visual Basic
      /// examples failed to work or even compile (once converted) in C#.
      /// </summary>
      /// <param name="styleText"></param>
      /// <returns></returns>
      private Word.Style getStyle(string styleText)
      {
         foreach(Word.Style aStyle in WordDocument.Styles)
         {
            //MessageBox.Show( aStyle.NameLocal );
            if (aStyle.NameLocal.StartsWith(styleText))
            {
               return aStyle;
            }
         }
         return null;
      }


      /// <summary>
      /// This function searches for the old content in the document in order to remove it
      /// prior to adding in the new content from the latest EA model data.
      /// </summary>
      private void removeExistingDocumentContent()
      {
         //WordApp.Visible = true;  // enable this when debugging this horrible function
         Word.Selection aSelection;
         object findStyle = getStyle("Heading 1");
    
         WordApp.Selection.WholeStory();
         aSelection = WordApp.Selection;

         aSelection.Find.ClearFormatting();
         aSelection.Find.set_Style( ref findStyle );
         aSelection.Find.Text = "Introduction";
         aSelection.Find.Replacement.Text = "";
         aSelection.Find.Forward = true;
         aSelection.Find.MatchCase = false;
         aSelection.Find.MatchWholeWord = false;
         aSelection.Find.MatchWildcards = false;
         aSelection.Find.MatchSoundsLike = false;
         aSelection.Find.MatchAllWordForms = false;

         object missingValue = Type.Missing;
         if (aSelection.Find.Execute(ref missingValue, ref missingValue, 
            ref missingValue, ref missingValue, ref missingValue, 
            ref missingValue, ref missingValue, ref missingValue, 
            ref missingValue, ref missingValue, ref missingValue, 
            ref missingValue, ref missingValue, ref missingValue, 
            ref missingValue)) 
         { 
            // found Introduction with Heading 1 style so we are now at the point in the 
            // document where everything here on must be deleted in readiness for the newly
            // generated content. Extend the range to the end of the document and cut out the
            // old content.
            Word.Range range = aSelection.Range;
            range.End = WordDocument.Content.End;
            range.Cut();
         }
      }


      /// <summary>
      /// Parses an element (because elements can contain sub-elements).
      /// This is also the function that identifies the occurrences of the special
      /// EA_DocGenXXX elements.
      /// </summary>
      /// <param name="theElement"></param>
      /// <param name="recurse_level"></param>
      private void parse_element(EA.Element theElement, int recurse_level)
      {
         // First look for and handle special elements that control the doc-gen process

         // The EA_DocGenPackageLink can contain a list of GUIDs in its notes section
         // that point to model structure outside of the localised structure forming the
         // document. This enables a document to grab content from other external models
         // as long as they are loaded into the repository.
         if (theElement.Name.StartsWith("EA_DocGenPackageLink"))
         {
            processPackageLink( theElement, recurse_level );
         }

            // The EA_DocGenDiagramLink can contain a list of GUIDs in its notes section
            // that point to diagram elements anywhere in the repository.
         else if (theElement.Name.StartsWith("EA_DocGenDiagramLink"))
         {
            processDiagramLink( theElement );
         }

            // The EA_DocGenDiagramLink can contain a list of GUIDs in its notes section
            // that point to diagram elements anywhere in the repository.
         else if (theElement.Name.StartsWith("EA_DocGenElementLink"))
         {
            processElementLink( theElement, recurse_level );
         }

            // The EA_DocGenTable element can contain table content in its notes section
            // and must be dealt with in a special way.
         else if (theElement.Name.StartsWith("EA_DocGenTable"))
         {
            processTableElement( theElement, recurse_level );
         }

            // The EA_DocGenText element can contain text content in its notes section
            // that must be appended to the doc in Body 1 style within the current
            // section.
         else if (theElement.Name.StartsWith("EA_DocGenText"))
         {
            processTextElement( theElement, recurse_level );
         }

         else if (theElement.Name.StartsWith("EA_DocGenRelationshipMatrix"))
         {
            processRelationshipMatrixElement( theElement, recurse_level );
         }

         else
         {
            generateElementContent( theElement, recurse_level );
         }      
      }


      /// <summary>
      /// Parses the package and all of its content, extracting information as needed, and
      /// adding it to the word document. This method is recursive, since recursion is the
      /// easiest way to walk thru' the package levels in the model.
      /// </summary>
      /// <param name="parentPackage"></param>
      /// <param name="recurse_level"></param>
      private void parse_package(EA.Package thePackage, int recurse_level)
      {
         // track recursion level to control heading style formatting requirements
         // The one-shot skiproot feature of the EA_DocGenPackageLink element handling has an effect
         // here, as you can see. 
         bool rootPackageWasSkipped = !oneShot_skipRootPackage;
         if (oneShot_skipRootPackage == false)
         {
            recurse_level++;
            trackDocSection(recurse_level);
         }

         if (recurse_level > 9)
         {
            throw new System.StackOverflowException("Max Recursion Depth (9) Exceeded" );
         }

         // generate package heading and description. This may, depending on the package name,
         // consume the elements in the package. If it does not, then consume them (and any diagrams)
         // locally here.
         if (false == generatePackageHeadingAndDescription( thePackage, recurse_level ))
         {
            // consume diagrams
            generatePackageDiagrams( thePackage );

            // consume elements - we have to use a special sorting class here because of peculiarties
            // in the way EA holds the elements in the collections. 
            EA_ElementSorter elementSorter = new EA_ElementSorter(thePackage);
            EA.Element theElement = null;
            int theElementsRelativeLevel = 0;
            if (true == elementSorter.getFirst(ref theElement, ref theElementsRelativeLevel))
            {
               do
               {
                  int theElementsRecurseLevel = recurse_level + theElementsRelativeLevel;

                  parse_element( theElement, theElementsRecurseLevel );

               } while (true == elementSorter.getNext(ref theElement, ref theElementsRelativeLevel));
            }
         }

         // Scan through the packages within this package.
         foreach(EA.Package lowerLevelPackage in thePackage.Packages)
         {
            // recurse
            parse_package(lowerLevelPackage, recurse_level);
         }
         

         if (rootPackageWasSkipped == false)
            recurse_level--;
      }


      /// <summary>
      /// This is the overall generate document method. It creates a word document from 
      /// an input template, sets various characteristics of the document, schedules the
      /// EA parsing function, before finally saving the document to the output file name.
      /// </summary>
      private void createTheWordDoc()
      {
         try 
         {
            object template       = this.textBox_template.Text;
            object newTemplate    = Type.Missing;
            object outputFilename = this.textBox_output_file.Text;

            object docType = Type.Missing;
            object visible = Type.Missing;
            object nothing = Type.Missing;
            object notTrue = false;

            EA_Repository.WriteOutput( Main.GUI_OUTPUT_TAB_NAME, "", 0);
            EA_Repository.WriteOutput( Main.GUI_OUTPUT_TAB_NAME, 
                                       String.Format( "Generating Document {0}", this.textBox_output_file.Text), 0 );

            // tell word not to show itself
            WordApp.Visible = this.checkBox_WordVisibility.Checked;
            
            // Create a document from the input template
            WordDocument = WordApp.Documents.Add(ref template, ref newTemplate, ref docType, ref visible);
   
            // Remove all old content from the input document/template. Note that this assumes
            // that content will begin at section 1 with the title "Introduction" and it will have
            // "Heading 1" style.
            removeExistingDocumentContent();

            // turn off grammar and spell checking
            WordDocument.GrammarChecked = false;
            WordDocument.SpellingChecked = false;
        
            // Parse EA_DocGen Document Model. The parent package is the document model itself
            // and the packages within it are the top level packages only.
            // Question: do we have to parse diagrams and elements at this top level? So far,
            // this has been found to be unecessary, but you never know. For now, dont do it.
            foreach(EA.Package thePackage in EA_ParentPackage.Packages)
            {
               parse_package(thePackage, 0);
            }

            // TODO
            // Can we run a word macro from this addin? If so, we should open the doc properties
            // dialog to let user enter/update the properties.
            // We could emulate the UpdateAllDocumentFields subroutine written in VB within the
            // ERG standard template. For now though, do some basic stuff only.

            // Select the entire document, and Update all fields, including Table of Contents
            // and then collapse the selection in case user wants to view the document, because
            // they wont want to see the inverse video of the selection.
            WordApp.Selection.WholeStory();
            WordApp.Selection.Fields.Update();
            object direction = Word.WdCollapseDirection.wdCollapseEnd;
            WordApp.Selection.Collapse(ref direction);

            // Save the document to the output file
            WordDocument.SaveAs( ref outputFilename, 
               ref nothing, ref nothing, ref nothing, ref nothing, 
               ref nothing, ref nothing, ref nothing, ref nothing,
               ref nothing, ref nothing, ref nothing, ref nothing,
               ref nothing, ref nothing, ref nothing);

            // Tell user the process completed ok
            textBox_progress.AppendText( "---Completed---" );
            MessageBox.Show("Document Generation Complete");
            

         }
         catch (Exception createTheWordDoc_exception)
         {
            MessageBox.Show("Document Generation Failed\n\n" + createTheWordDoc_exception.Message);
         }
      }

      /// <summary>
      /// Generates the text for requirements.  This uses custom styles to format the text 
      /// rather than formatting it in code.
      /// </summary>
      private void generateRequirementText( EA.Element theElement )
      {
         string reqID = "";
         string reqShortDesc = "";
         string reqNotes = "";
         string reqStatusText = "";
         string reqHeadingStyle ="";
         string reqParaStyle = "";
         object reqNameStyle = "Requirements Name";
         Word.Range shortDescRange;

         // Set the style depending on the status
         switch ( theElement.Status )
         {
            case "Proposed":
               reqStatusText = "(Proposed)";
               reqHeadingStyle = "Requirements Proposed Heading";
               reqParaStyle = "Requirements Proposed Body";
               break;

            case "Rejected":
               reqHeadingStyle = "Requirements Rejected Heading";
               reqParaStyle = "Requirements Rejected Body";
               break;

            case "Approved":
               reqStatusText = "(Phase " + theElement.Phase + ")";
               reqHeadingStyle = "Requirements Approved Heading";
               reqParaStyle = "Requirements Approved Body";
               break;

            default:
               reqStatusText = "(" + theElement.Status + ")(Phase " + theElement.Phase + ")";
               reqHeadingStyle = "Requirements Approved Heading";
               reqParaStyle = "Requirements Approved Body";
               break;
         }

         // Pull out the ID from the name
         int pos = theElement.Name.IndexOf( " ", 0, theElement.Name.Length );
         reqID = theElement.Name.Substring( 0, pos );

         // Pull out the short description from the rest of the name
         reqShortDesc = theElement.Name.Substring( pos, theElement.Name.Length-pos );
         reqShortDesc = reqShortDesc.Trim();

         // Pull out the notes
         reqNotes = theElement.Notes.ToString();

         // Add the text
         appendAndSelectText( reqID + '\t', reqHeadingStyle );
         appendAndSelectText( reqShortDesc, reqHeadingStyle, true );
         shortDescRange = WordRange;
         appendAndSelectText( '\t' + reqStatusText, reqHeadingStyle, true );

         //reapply the name char style to the short desc.
         shortDescRange.Start = shortDescRange.Start-1;
         shortDescRange.End = WordRange.Start-1;
         shortDescRange.set_Style( ref reqNameStyle );

         appendAndSelectText( reqNotes, reqParaStyle );

      }


      public class elementSortByName : IComparer
      {
         int IComparer.Compare( object x, object y)
         {
            if (x!=null & y!= null)
               return (new CaseInsensitiveComparer()).Compare( ((EA.Element)x).Name, ((EA.Element)y).Name );
            else
               return 0;
         }
      }

      /// <summary>
      /// This function will insert a relationship matrix table into the document, built from content
      /// specified in options that are provided in the notes section of the special element that
      /// has led to this function being called.
      /// </summary>
      /// <param name="theElement"></param>
      /// <param name="recurse_level"></param>
      private void processRelationshipMatrixElement( EA.Element theElement, int recurse_level )
      {
         int tableNum = 0;
         Word.Table table = null;

         // process the options for the relationship matrix
         EA_RelationshipMatrix EA_RelMatrix = new EA_RelationshipMatrix(EA_Utils);
         if (!EA_RelMatrix.processRelationshipMatrixOptions( theElement ))
         {
            return;  // must have been an error
         }

         // Scan the fromPackage to find out how many elements are present and so how many
         // rows the matrix table will need
         ElementAccumulator fromLister = new ElementAccumulator(EA_RelMatrix.fromElementTypes);
         EA_Utils.findAndProcessPackageElements( EA_RelMatrix.fromPackage, fromLister, EA_RelMatrix.fromPackageRecursion );

         // Sort the "from" elements
         elementSortByName sorter = new elementSortByName();
         fromLister.Elements.Sort( sorter );
         
         // dictionary to support to-from table construction.
         ArrayList fromToDictionary = new ArrayList();

         // create the table in the word doc
         if (EA_RelMatrix.fromToTableTitle != null)
         {
            if (EA_RelMatrix.fromIntroText != null)
            {
               appendAndSelectText( EA_RelMatrix.fromIntroText, "Body 1" );
            }

            tableNum = createTable( EA_RelMatrix.fromToTableTitle, fromLister.Elements.Count + 1, 2 );
            table = WordDocument.Tables[tableNum];
            
            table.Cell(1,1).Range.Text = EA_RelMatrix.fromColumnTitle;
            table.Cell(1,1).Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
            table.Cell(1,1).Range.Font.Bold = 1;
            table.Cell(1,2).Range.Text = EA_RelMatrix.toColumnTitle;
            table.Cell(1,2).Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
            table.Cell(1,2).Range.Font.Bold = 1;
         }

         int row = 2;
         foreach(EA.Element fromElement in fromLister.Elements)
         {
            if (EA_RelMatrix.fromToTableTitle != null)
               table.Cell(row,1).Range.Text = fromElement.Name;
 
            // look at the elements connection collection to find references to the
            // destination elements
            EA.Collection conCollection = fromElement.Connectors;
            bool firstCon = true;
            foreach (EA.Connector thisCon in conCollection)
            {
               EA.Element destE = EA_Repository.GetElementByID( thisCon.SupplierID );
               if (destE == null)
                  continue;

               // if the destination element is of a type that the user has requested to include...
               if (!EA_RelMatrix.toElementTypes.Contains( destE.Type ))
                  continue;

               if ( (destE.Type == "Requirement") && (destE.Status == "Rejected") )
                  continue;

               // Output the text
               if (firstCon)
               {
                  firstCon = false;
                  if (EA_RelMatrix.fromToTableTitle != null)
                     table.Cell(row,2).Range.Text = destE.Name;
               }
               else
               {
                  if (EA_RelMatrix.fromToTableTitle != null)
                     table.Cell(row,2).Range.Text += destE.Name;
               }

               // Capture the from-to relationship in a dictionary where the key is the
               // ID of the destination element, and the value is the from element name.
               // This dictionary will enable rapid construction of the to-from table if
               // the user has requested it, from the exact same relationship info used
               // for the from-to table.
               if (EA_RelMatrix.toFromTableTitle != null)
               {
                  DictionaryEntry newEntry = new DictionaryEntry(destE.ElementID, fromElement.Name);
                  fromToDictionary.Add( newEntry );
               }
            }
            row++;
         }

         // Does user want a to-from table ?
         if (EA_RelMatrix.toFromTableTitle != null)
         {
            ElementAccumulator toLister = new ElementAccumulator(EA_RelMatrix.toElementTypes);
            EA_Utils.findAndProcessPackageElements( EA_RelMatrix.toPackage, toLister, EA_RelMatrix.toPackageRecursion );

            // Sort the "to" elements
            toLister.Elements.Sort( sorter );

            if (EA_RelMatrix.toIntroText != null)
            {
               appendAndSelectText( EA_RelMatrix.toIntroText, "Body 1" );
            }

            // create the table in the word doc
            tableNum = createTable( EA_RelMatrix.toFromTableTitle, toLister.Elements.Count + 1, 2 );
            table = WordDocument.Tables[tableNum];
            
            table.Cell(1,1).Range.Text = EA_RelMatrix.toColumnTitle;
            table.Cell(1,1).Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
            table.Cell(1,1).Range.Font.Bold = 1;
            table.Cell(1,2).Range.Text = EA_RelMatrix.fromColumnTitle;
            table.Cell(1,2).Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
            table.Cell(1,2).Range.Font.Bold = 1;

            // To make the to-from table, we use the dictionary that was built when making the from-to
            // table. The dictionary will allow rapid determination of what "from" items belong to each
            // "to" item, without us having to go back to EA and enquire on the database.
            row = 2;
            foreach(EA.Element fromElement in toLister.Elements)
            {
               // from 
               table.Cell(row,1).Range.Text = fromElement.Name;

               // skip rejected requirements - not needed because it is done in the ElementAccumulator class
               //if ( (fromElement.Type == "Requirement") && (fromElement.Status == "Rejected") )
               //   continue;

               // to's
               bool firstCon = true;
               foreach (DictionaryEntry de in fromToDictionary)
               {
                  if ((int)de.Key == fromElement.ElementID)
                  {
                     if (firstCon)
                     {
                        firstCon = false;
                        table.Cell(row,2).Range.Text = (string)de.Value;
                     }
                     else
                     {
                        table.Cell(row,2).Range.Text += (string)de.Value;
                     }
                  }
               }
               row++;
            }
         }
      }

   

      #endregion
   }
}