Original: December 28, 2003
Updated: July 31, 2005
The source code for the production release of UncleGED is written in Micrsoft Visual Basic 5/6. This was later ported over to VB.NET using the Upgrade Wizard (UncleGED rls 6). Once inside of VB.NET, I converted the main program over from a Windows (WinForms) application to a Command-Line (Console) application. I then went to the next step of porting the code to C#. The published source provided is written in C# and is labled UncleGED rls 7 (0.7). (See Why C#? under Notes On Source)
UncleGED rls 7 is written in the .NET language C# and requires that the Microsoft .NET Framework 1.1 be installed on the user's PC. To ensure adequate performance, the .NET Framework has the following minimum and recommended system requirements for applications. Operating System Requirements The .NET Framework is supported on the following platforms.
Note On all these systems, Microsoft® Internet Explorer 5.01 or later and Microsoft® Windows® Installer 2.0 or later are also required. Hardware Requirements
*Or the minimum required by the operating system, whichever is higher.
To obtain and install the Microsoft .NET Framework 1.1, please visit this Microsoft site "How to Get the Microsoft .NET Framework" (see also How To Build UncleGED)
To build UncleGED you will need to download and install the Microsoft .NET Framework 1.1 SDK or use Visual Studio .NET 2003.
To build from the command line use the make.bat file or enter from the command line:
csc /out:uged.exe /t:exe *.cs /debug /r:System.dll;System.Data.dll;System.XML.dll
The UncleGED source consists of the following files:
Now A Command-line Program
Unlike previous versions, Uncle GED 7 is console-based program that is designed to be run from the DOS command-line. It does not present a standard Windows user-interface. Some user's may find this a little too retro for their tastes, however Uncle GED 7 is very easy to use and is much more flexible than previous versions.
Uncle GED was originally designed to do one thing- take the input from a GEDCOM file and output the data found in the GEDCOM file into a series of HTML files. Subsequent versions of UncleGED added some little extras along the way, but it primary purpose remained the same: GEDCOM-to-HTML.
In a sense the command-line version of Uncle GED goes back to the basics of its original purpose with all of the fixes and enhancements that were added into versions 2 through 5. Gone, however, are some of the side enhancements such as the catalog feature and the book feature.
To use Uncle GED you simply open up a command prompt window in the UncleGED directory and type uged followed by the name of the gedcom file.
Examples:
uged /f:gedcom\abigged.ged
or
uged /f:"c:\program files\uncleged\uncleged6\gedcom\asmallged.ged"
Note the double-quotes around the file path and name in the second example. This is required if there is a space in the path (as in "program files") The file path and file name can be any valid file path and name.
If you had a GEDCOM file called myged.ged located
in the ancestors directory on drive D: you could enter...
uged /f:d:\ancestors\myged.ged ...
and UncleGED would open and process that file.
HTML Output
Where's does the output go? By default it goes into a "html" sub-directory under the UncleGED directory. By default this would be ".\html". However this directory can be changed to any valid directory by editing in the uged_config.xml file and changing the "HtmlPath" entry.
Custom Configurations
A new feature introduced in Uncle GED 7 is the ability to specify a custom configuration. You can now copy and modify the uged_config.xml file and then tell Uncle GED to use a different configuration. This is done by adding the new config file to the command line.
Example: process "d:\ancestors\myged.ged" and use
mycustom_config.xml as the config file:
uged /f:d:\ancestors\myged.ged /c:".\mycustom_config.xml"
What this does is allow you to create batch files to process your gedcom into html using a specific configuration. You could then add to these batch files the necessary intructions to upload the HTML output to your website via FTP.
/f[GEDCOM FILE]
/c[alternate XML CONFIG FILE to use]
/w create a default XML config file
BOOLEAN OPTIONS - These act as switches that reverses the
default setting
-ac - AddUserCopyright (default:False)
-ak - AddKeywords (default:True)
-as - AddSurnameCount (default:True)
-cl - ChronoLinks (default:True)
-cp - CreateChronoPage (default:True)
-de - DoDateEstimation (default:False)
-dm - DisplayMRIN (default:True)
-dr - DisplayRIN (default:True)
-gf - CreateGendexFile (default:False)
-hr - HiLiteMostRecent (default:True)
-io - IgnoreOccupations (default:True)
-ld - LocDisplayEventDates (default:False)
-lo - LocReverseOrder (default:True)
-lp - CreateLocPage (default:True)
-lr - CreateListReport (default:True)
-lt - OccLocEqualsOccText (default:False)
-mi - MostRecentOnIndex (default:True)
-mp - MostRecentOnPage (default:True)
-op - CreateOccupationsPage (default:True)
-pr - Privacy (default:True)
-sc - SurnamesAllCaps (default:True)
-sp - CreateStatsPage (default:True)
-ub - UseBackground (default:True)
-ur - UseBanner (default:True)
-wa - WarnNonAnsi (default:False)
-xl - ExcludeLivingIndividualsAfterBirthCutOff (default:False)
-xn - ExcludeNotes (default:False)
COMMAND OPTIONS - These require additional input
-b[BackgroundFile] - BackgroundFile
-c[ChronoCutOff] - ChronoCutOff
-e[AuthorsEmail] - AuthorsEmail
-h[HtmlPath] - HtmlPath
-n[AuthorsName] - AuthorsName
-p[FamFilePrefix] - FamFilePrefix
-r[BannerFile] - BannerFile
-s[0|1|2] - LocEstimateMethod
-t[MainPageTitle] - MainPageTitle
-u[AuthorsUrl] - AuthorsUrl
-y[BirthCutOffYears] - BirthCutOffYears
Example configuring UncleGED from the command line:
uged /fmyged.ged -n"Joe Blow" -uhttp://mywebsite.com/~joeb -ac
Configuration options for UncleGED 7 are stored in a XML file which by default is called uged_config.xml. You can create a default config file from the command-line by running UncleGED with the /w option.
Option |
Default |
Description |
Privacy |
TRUE |
Mark information for individuals currently living as private. With this option on any data (notes, etc.) for individual with a valid birth date less than 80 years will be marked as private. [private]. However to best address privacy issues it better to filter your GEDCOM at the time that you export it from your genealogy software.
|
AuthorsEmail |
{blank} |
The email address of the GEDCOM's author |
AuthorsName |
{blank} |
The GEDCOM's author's name |
AddUserCopyright |
FALSE |
When this option is set to TRUE, Uncle GED displays the Author's name and email within a copyright statement at the bottom of each page.
|
DisplayMRIN |
FALSE |
Display MRIN after marriage - When this option is selected, Uncle GED displays the MRIN (marriage reference number) in the title of the family HTML page.
|
DisplayRIN |
FALSE |
Display RIN after name - When this option is selected, Uncle GED displays the RIN (individual reference number) after eachindividuals name.
|
AuthorsUrl |
{blank} |
A link to this page will be added to the table contents page (index.htm). Prior to rel. 4.01 this pointed to ../index.htm. You may now link to any page that you wish. if left blank, the link will not be added to the page.
|
CreateGendexFile |
FALSE |
When this option is selected, Uncle GED creates a GENDEX file called GENDEX.txt in the html directory. For more information on GENDEX files and their purpose visit Welcome to GENDEX .
|
MainPageTitle |
Family Page |
As of UncleGED 4.01 you may now specify the main page title.
|
Option |
Default |
Description |
UseBackground |
FALSE |
Use background image (images/ug_bkgrd.gif). With this option the ug_bkgrd.gif file located normally “C:\PROGRAM FILES\UNCLEGED\HTML\IMAGES” will be used as the background for all HTML files produced by UncleGED. NOTE: As of UncleGED 4.01, you may specify any background image and location for that image that you wish.
|
UseBanner |
FALSE |
Use banner image (images/ug_banner.gif). With this option the ug_banner.gif file located normally “C:\PROGRAM FILES\UNCLEGED\HTMl\IMAGES” will be used as the banner for all HTML files produced by UncleGED. NOTE: As of UncleGED 4.01, you may specify any banner image and location for that image that you wish. The size that I used for my banner is 224 X 38, but you may use any size that you like.
|
BackgroundFile |
images/ug_bkgrd.gif |
Relative or absolute URL location of background file
|
BannerFile |
images/ug_banner.gif |
Relative or absolute URL location of banner file
|
Option |
Default |
Description |
ExcludeNotes |
FALSE |
Excludes notes from HTML pages. With option set to true any notes found in the GEDCOM file are excluded from the HTML pages
|
AddKeywords |
FALSE |
Surnames As Keywords On Index Page. When this option is selected, Uncle GED adds all surnames found with a count greater than five to the meta sections of the index page….
|
WarnNonAnsi |
FALSE |
There is a optional setting in UncleGED that warns about non-ANSI files The warning reads: "WARNING: This file has been saved in a character format other than ANSI and as a result some characters may not translate correctly. While UncleGED should not have any problem processing the file, it works best with GEDCOM files saved in the ANSI character format." Also, UncleGED cannot process GEDCOM files that do not contain carriage returns and line feeds (CR/LF). Some GEDCOM files downloaded from the internet may only contain line feed characters. If you try to open one of these files with UncleGED you will see the following message: "File is possibly corrupt and can not be read. It is missing carriage returns." TIP: Try opening the file with a word processor and re-saving the file as text only.
|
AddSurnameCount |
FALSE |
Add Surname Count to Surnames Page. When this option is selected, Uncle GED adds a count to each surname displayed on the surname page.
|
CreateListReport |
FALSE |
When this option is set to TRUE, Uncle GED creates a listing file when HTML pages are created. This listing file is a comma-delimited file that lists the individual's name, their RIN, and which HTML they appear in. Look for list.txt in the UncleGED application directory.
|
CreateStatsPage |
FALSE |
When this option is selected, Uncle GED creates a statistics page that lists the statistics for the GEDCOM file. Look for stats.htm in the HTML directory.
|
CreateChronoPage |
FALSE |
Create Chronology Of Events Page - When this option is selected, Uncle GED will create a Chronology of Events page.
|
ChronoCutOff |
1900 |
Last date (year) for the chronology listings. Any year after the ChronoCutOff date will not be listed in the chronology.
|
FamFilePrefix |
fam |
As of UncleGED 5, the "family file prefix" feature allows the user to designate a 3 character naming convention for the files. This is useful if you want to split your maternal and paternal lines. For example I have one set of files that follow the pattern of debxxx.html for my father's side and another set with the pattern dobxxx.html for my mother's. If you set this three character prefix to something other than "fam" (for example "deb") then all famxxx.html files become debxxx.html and the other files will have the family file prefix prepended to them (for example locations.html becomes deb_locations.html and so forth).
|
ChronoLinks |
TRUE |
Adds links for the family pages to the chronology page.
|
SurnamesAllCaps |
FALSE |
Display Surnames In Upper-Case
|
MostRecentOnIndex |
TRUE |
Display Last Update For Individual On Index - Besides displaying the individual's name and birth date on the index, Uncle GED optionally displays the last date that this individuals record was updated.
|
MostRecentOnPage |
TRUE |
Display Most Recent Change Date On Family Pages - When this option is selected, Uncle GED displays the most recent change date at the bottom of each family page. Example: The most recent update of information contained on this page was on: 15 July 2001 The date displayed here is determined by examining the change dates stored in the GEDCOM file for each of the individuals displayed uniquely on this page.
|
HiLiteMostRecent |
TRUE |
Make Bold Most Recent Updates On Index - When this option is selected along with the option described above, Uncle GED will display all changes made in the last 90 days in formatting in bold. NOTE: The change dates are provided in GEDCOM files created with PAF and may not be in files produced by other software vendors. For change dates UncleGED looks for something like the following in the GEDCOM file: 1 CHAN 2 DATE 24 Feb 2002 3 TIME 12:34:12
|
CreateLocPage |
FALSE |
There are a number of options available for creating a listing of locations and place names. An example of this page can be found at Example Locations Page. Selecting the "Create Locations Page" option will not only create this style of HTML page, but will also create a file called "loc_report.txt". The "loc_report.txt" provides a listing of all locations and place names found in your GEDCOM file and can be useful in locating problems with place names in the GEDCOM. Setting CreateLocPage to true creates the files listed above.
|
LocDisplayEventDates |
FALSE |
Display Event Dates On Locations Page - displays a date (if available) between the individuals name and the event description.
|
LocEstimateMethod |
0 |
Estimated Locations - Although not a part of the GEDCOM specification, there is a format used by the LDS in Ancestral files for entering "estimated" locations and that is to enter the location surrounded by the "<" and ">" characters. UncleGED automatically converts the "<" and ">" characters to "[" and "]" characters. The following options regarding estimated locations are as follows: 0. Do Not List Estimated Locations - locations surrounded by the "[" and "]" characters are not displayed. 1. Treat Estimated Locations The Same As Actual Locations - the "[" and "]" characters are dropped from the estimated locations and all locations are displayed together. 2. List Estimated Locations At Bottom Of Page - locations surrounded by the "[" and "]" characters are retained and are displayed at the bottom of the page.
|
LocReverseOrder |
TRUE |
List Locations In Reverse Order - when selected an entry such as "Houston, Harris, Texas" will be displayed as "Texas, Harris, Houston".
|
IgnoreOccupations |
FALSE |
Do Not Print Occupations On Locations Page - Occupation listing are excluded from the Locations page (If the source of your GEDCOM file is FTW (Family Tree Maker) then set this option to TRUE; otherwise leave FALSE).
|
CreateOccupationsPage |
FALSE |
Create Occupations Page - creates the occupation.html file.
|
OccLocEqualsOccText |
FALSE |
Occupation Location Equals Occupation Text - This rather wordy option basically means that both the location and the description of the occupation are within the same field in the GEDCOM file. (If the source of your GEDCOM file is FTW (Family Tree Maker) then set this option to true; otherwise leave cleared ).
|
HtmlPath |
{app path\}html |
Specify the directory to which the family pages and other HTML files will be written.
|
DoDateEstimation | FALSE |
When this option is selected UncleGED with attempt estimate the birth date for individuals without birthdates.
|
ExcludeLivingIndividualsAfterBirthCutoff | FALSE |
When this option is selected UncleGED will exclude any individual from final processing if the individual's birthdate is less than BirthCutoffYears from the current year. In other words if the BirthCutoffYears is set to 80 and the current year is 2003 then any individual born in or after 1923 will be excluded from the HTML pages created.
|
BirthCutoffYears | 80 | See the setting ExcludeLivingIndividualsAfterBirthCutoff |
The following options are no longer supported or
used by Uncle GED 7: DisplayOptionBeforeBuild, AppendCatalog, TabDelimit,
CatalogPath, GedcomPath and BookPath.
The simple answer is that I wanted / needed to learn C# and the best way I could think of doing it was to take an existing program written in VB that I was very familar with and attempt to port it to C#.
My main reason for doing is in the hope that someone might aide me in creating versions of UncleGED that produce HTML output in alternate languages (French, German, Spanish, etc.)
For the initial port I used a free VB to C# translation tool called BabelFisken by Lars Johansson. This resulted in at least translating the majority of the code over from VB.NET to C#, however there was a considerable amount of clean-up required. Here is a list of some of the issues that I had to deal with:
After a few days of hacking I was finally able to get the code to compile
and successfully run. Porting UncleGED over to C# also kicked over a hornet's
nest (so to speak) and unleashed a whole slew of "hidden" bugs in
UncleGED. A lot of this involved "errors" that were being ignored due
the use of On Error Resume Next in the VB
versions.
I ran a series of tests on the code using GEDCOM files exported from LEGACY, PAF, FTW, and TMG and everything appears to be in working order; however I'm convinced that there are few more yet uncovered bugs in this source, but then if there weren't any bugs in the code what fun would there be in playing with this code? :).
Internally the configuration settings are stored in the "UgedApplicationSettings" class.
Originally in the VB6 version of UncleGED configuration settings were saved in the Windows registry (only because Microsoft told me that was the way things should be done c.1995). This was later changed to using a good old INI file. When going to .NET, rather than use the .NET app.config to store configuration settings (or revert back to reading/writing the Registry), I found that a "quick trick" was to make the Configuration class serializable and this allowed for easily "dumping" the contents of the class out to an XML file.
Here is an example of how this is done in the VB.NET version:
Public TheApp As New UgedApplicationSettings ..... '(Populate the configuration properties) ..... 'method for saving the app settings to an XML file Public Sub SaveXmlSettings(ByRef sXmlConfig As String) Dim XmlS As New XmlSerializer(GetType(UgedApplicationSettings)) Dim fStream As New IO.FileStream(sXmlConfig, IO.FileMode.Create) XmlS.Serialize(fStream, TheApp) fStream.Close() fStream = Nothing XmlS = Nothing End Sub 'method for reading the app settings from an XML file Public Function LoadXmlSettings(ByRef sXmlConfig As String) As UgedApplicationSettings Dim myAppSettings As UgedApplicationSettings Try Dim XmlS As New XmlSerializer(GetType(UgedApplicationSettings)) Dim fStream As New IO.FileStream(sXmlConfig, IO.FileMode.Open) myAppSettings = CType(XmlS.Deserialize(fStream), uged.UgedApplicationSettings) fStream.Close() fStream = Nothing XmlS = Nothing Catch ex As Exception myAppSettings = Nothing End Try Return myAppSettings End Function |
Here is that same code in C#
UgedApplicationSettings configSettings = new UgedApplicationSettings(); ..... //(Populate the configuration properties) ..... public static void SaveXmlSettings(string sXmlConfig, UgedApplicationSettings ConfigSettings) { XmlSerializer XmlS = new XmlSerializer(typeof(UgedApplicationSettings)); FileStream fStream = new FileStream(sXmlConfig, FileMode.Create); XmlS.Serialize(fStream, ConfigSettings); fStream.Close(); } public static UgedApplicationSettings LoadXmlSettings(string sXmlConfig) { UgedApplicationSettings configSettings; try { XmlSerializer XmlS = new XmlSerializer(typeof(UgedApplicationSettings)); FileStream fStream = new FileStream(sXmlConfig, FileMode.Open); configSettings = (UgedApplicationSettings)XmlS.Deserialize(fStream); fStream.Close(); fStream = null; XmlS = null; } catch { configSettings = null; } return configSettings; } |
I found the first few of these methods on the web (see the article Overloading strings in C#) and added a few other VB/VB.NET type functions that I needed to replace in UGed keeping in mind that I could have just have easily re-wrote the portion of the code that used a VB function such as Trim(string) with string.Trim(), but in the interest of time and the desire to just get the damn-thing to compile and run, I elected to create replacement functions so I could just use "search-and-destroy" to replace the original VB function calls with their C# replacements.
using System; namespace UGed { public class StringFuncs { public StringFuncs() { //constructor } public string Left(string inputString, int strLen) { if (inputString.Length > strLen) { string retValue = inputString.Substring(0, strLen); return retValue; } else { return inputString; } } public string Right(string inputString, int strLen) { if (inputString.Length > strLen) { string retValue = inputString.Substring(inputString.Length - strLen, strLen); return retValue; } else { return inputString; } } public string Mid(string inputString, int startIndex, int strLen) { string retValue = inputString.Substring(startIndex - 1, strLen); return retValue; } public string Mid(string inputString, int startIndex) { string retValue = inputString.Substring(startIndex - 1); return retValue; } public int Len(string inputString) { return inputString.Length; } public string UCase(string inputString) { return inputString.ToUpper(); } public string LCase(string inputString) { return inputString.ToLower(); } public int InStr(string String1, string String2) { int retValue = String1.IndexOf(String2) + 1; return retValue; } public int InStr(int Start, string String1, string String2) { int retValue = String1.IndexOf(String2, Start - 1) + 1; return retValue; } public string Trim(string inputString) { try { if (inputString.Length > 0) { return inputString.Trim(); } else { return ""; } } catch { return ""; } } public DateTime DateValue(string dateString) { DateTime dateValue = System.DateTime.Parse(dateString); return dateValue; } public bool IsDate(string inValue) { bool bValid; try { DateTime myDT = DateTime.Parse(inValue); bValid = true; } catch //(FormatException e) { bValid = false; } return bValid; } } } |
A collection is an object that contains a set of related objects. It is a class module that exists solely to group all the objects of another class.
Here is the Individuals collection class as written in VB6 (slightly modified from the collection class as generated by the VB Class Builder add-in):
Option Explicit Private mCol As Collection Public Function Add(NewIndividual As Individual) As Boolean Dim sKey As String On Error GoTo AddIndividual_EH Add = True sKey = NewIndividual.ID 'the next line will fail if the key is duplicated mCol.Add NewIndividual, sKey Exit Function AddIndividual_EH: Add = False End Function 'the Get property allows the Individual object to be retrieved 'by using either the string Key or by using a numeric index '(ex. theIndividual = MyIndividuals("@I1@") or theIndividual = MyIndividuals(0)) Public Property Get Item(vntIndexKey As Variant) As Individual Set Item = mCol(vntIndexKey) End Property Public Property Get Count() As Long Count = mCol.Count End Property Public Sub Remove(vntIndexKey As Variant) mCol.Remove vntIndexKey End Sub 'NewEnum allows for accessing the Indvidual objects using For Each Public Property Get NewEnum() As IUnknown Set NewEnum = mCol.[_NewEnum] End Property Private Sub Class_Initialize() Set mCol = New Collection End Sub Private Sub Class_Terminate() Set mCol = Nothing End Sub |
Here is that same collection class after upgrading to VB.NET using the Upgrade Wizard:
Imports System Imports Microsoft.VisualBasic Friend Class Individuals Implements System.Collections.IEnumerable Private mCol As Collection 'a VB Collection Public Function Add(ByRef NewIndividual As Individual) As Boolean Dim sKey As String Try sKey = NewIndividual.ID mCol.Add(NewIndividual, sKey) Return True Catch ex As Exception Return False End Try End Function 'the Item property allows the Individual object to be retrieved 'by using either the string Key or by using a numeric index 'Note that it is declared as default so you do not need to include '"Item" when using: '(ex. theIndividual = MyIndividuals("@I1@") or theIndividual = MyIndividuals.Item("@I1@")) Default Public ReadOnly Property Item(ByVal vntIndexKey As Object) As Individual Get Item = CType(mCol.Item(vntIndexKey), Individual) End Get End Property Public ReadOnly Property Count() As Integer Get Count = mCol.Count() End Get End Property 'GetEnumerator allows for accessing the Indvidual objects using For Each Public Function GetEnumerator() As System.Collections.IEnumerator _ Implements System.Collections.IEnumerable.GetEnumerator GetEnumerator = mCol.GetEnumerator End Function Public Sub Remove(ByRef vntIndexKey As Integer) mCol.Remove(vntIndexKey) End Sub Public Sub Remove(ByRef vntIndexKey As String) mCol.Remove(vntIndexKey) End Sub Public Sub New() MyBase.New() mCol = New Collection End Sub Protected Overrides Sub Finalize() MyBase.Finalize() End Sub End Class |
Now here is the same collection class written in C# (this is a variation of the C# collection class(es) described in Franceso Balena's article Create .NET Custom Collections):
using System; using System.Collections; using System.Collections.Specialized; public class Individuals: System.Collections.Specialized.NameObjectCollectionBase { Hashtable ht = new Hashtable(); public bool Add(Individual NewIndividual) { string key; try { key = NewIndividual.ID; ht.Add(key, null); //will fail if duplicate key this.BaseAdd(key, NewIndividual); return true; } catch { return false; } } public void Clear() { this.BaseClear(); } // the Remove method that takes a string key public void Remove(string key) { this.Remove(key); } // the Remove method that takes a numeric index public void Remove(int index) { this.Remove(index); } // the Item property that takes a string key public Individual this[string key] { get { return (Individual) this.BaseGet(key); } set { this.BaseSet(key, value); } } // the Item property that takes a numeric index public Individual this[int index] { get { return (Individual) this.BaseGet(index); } set { this.BaseSet(index, value); } } public new IEnumerator GetEnumerator() { // return an instance of the inner enumerator object return new myEnumerator(this); } private class myEnumerator: IEnumerator { // a reference to the parent object Individuals parent; Individual currObject; int index = 0; public myEnumerator(Individuals fc) { this.parent = fc; } public bool MoveNext() { if (index < parent.Count) { currObject = parent[index]; index++; return true; } else { index = 0; return false; } } public object Current { get { // just return the line read by MoveNext return currObject; } } public void Reset() { // this method is never called and can be left empty } } } |