// Para Compare Versions

/* 
- Comparing of two versions in the same module or in different modules.
- Comparing of all attributes or only selected ones.
- Browsing of the objects in the order they are contained in the modules.
- Inserting of deleted objects behind the previous predecessor.
- Showing information about the versions and attributes in the modules.
- Showing changes as untangled markup including unchanged attributes.
- Exporting of a version of a module as a rich text file.
*/

//**********************************  History  ******************************
// Jürgen Albrecht  2008-01-15  Base version
// Reinhold Lauer   2010-10-19  Included added/deleted attributes
// Reinhold Lauer   2010-10-29  Added entire main column option
//                              Added unchanged attributes option
// Reinhold Lauer   2010-11-19  Added registry support
//                              Reduced export file size
//                              Improved comparision readability
// Reinhold Lauer   2011-01-11  Merged deleted objects
// Reinhold Lauer   2011-01-17  Update after replies on Doors forum

pragma runLim, 0

/////////////////////////////////////////////////////////////////////
const string sColorTable = "{\\colortbl ;\\red255\\green0\\blue0;\\red0\\green125\\blue0;\\red0\\green0\\blue128;\\red125\\green125\\blue125;\\red0\\green205\\blue205;}"
const string sBlack = "\\cf0 "
const string sRed = "\\cf1 "
const string sGreen = "\\cf2 "
const string sBlue = "\\cf3 "
const string sGray = "\\cf4 "
const string sCyan = "\\cf5 "
const string saColors[] = {"Black", "Gray", "Red", "Blue", "Cyan"}
const string saColorFonts[] = {sBlack, sGray, sRed, sBlue, sCyan}
const string sWhiteBack = "\\chcbpat0 "
const string sRedBack = "\\chcbpat1 "
const string sGreenBack = "\\chcbpat2 "
const string sBlueBack = "\\chcbpat3 "
const string sGrayBack = "\\chcbpat4 "
const string sCyanBack = "\\chcbpat5 "
const string saColorBacks[] = {sWhiteBack, sGrayBack, sRedBack, sBlueBack, sCyanBack}
string sRemovedStyle = "\\cf1\\strike "
string sRemovedBack = sRedBack
string sAddedStyle = sBlue
string sAddedBack = sBlueBack

const string sTopBorder = "\\clbrdrt\\brdrs\\brdrw15 "
const string sLeftBorder = "\\clbrdrl\\brdrs\\brdrw15 "
const string sRightBorder = "\\clbrdrr\\brdrs\\brdrw15 "
const string sBottomBorder = "\\clbrdrb\\brdrs\\brdrw15 "
string sIdentifierBorder = ""
string sOldIdentifierBorder = ""
string sTextBorder = sTopBorder sRightBorder
string sAllBorders = sTopBorder sLeftBorder sRightBorder sBottomBorder

string sRegistryPath = "HKEY_CURRENT_USER\\SOFTWARE\\Para\\Compare Versions\\"
const int iBloodyBig = 5000

Module mNewerModule = null
Module mOlderModule = null
Module mNewerVersion = null
Module mOlderVersion = null
string sNewerModuleName = ""
string sOlderModuleName = ""
Stream strmOutput
bool bShowUnchangedAttributes = true
bool bShowAllMainAttributes = true
bool bJustExport = false
string sPreviousObjectId = ""

Regexp findColorTable = regexp "colortbl[^}]*}"
Regexp hasWhiteSpace = regexp " |[\n]"

DB dbCompareVersions = null
DBE lstNewer = null
DBE fldNewerVersion = null
DBE lstOlder = null
DBE fldOlderVersion = null
DBE mlstAttrNames = null
DBE btnLoadVersions = null
DBE btnCompare = null
DBE btnBrowseOlder = null
DBE cbShowUnchangedAttributes = null
DBE cbShowAllMainAttributes = null
DBE chcNewerColor = null
DBE cbUnderlined = null
DBE chcOlderColor = null
DBE cbStrikeThrough = null

DBE btnSelectAllAttr = null
DBE btnDeselectAllAttr = null
DBE btnSelectAsView = null
DBE fldSelectedAttrCount = null
DBE btnSelectMatchingAttr1 = null
DBE btnSetMatchingAttr1 = null
DBE fldRegexPatternAttr1 = null
DBE btnSelectMatchingAttr2 = null
DBE btnSetMatchingAttr2 = null
DBE fldRegexPatternAttr2 = null
DBE btnSelectMatchingAttr3 = null
DBE btnSetMatchingAttr3 = null
DBE fldRegexPatternAttr3 = null

DBE fnOutputFileBrowse = null


// *************************************************************************************************************************
// Help

// Show the help dialog box. 
void applyShowHelp(DB db)
{
	string sHeading = "\\b\\ul\\li0 "
	string sNormal = "\\b0\\ul0\\li180 "
	string sIndent = "\\b0\\ul0\\li360 "
	string sHelpText = 
	sHeading "General\n\n"//-
	sNormal "Objects are compared in the order as they are contained in the newer version. "//-
			"Deleted older objects follow the previous older object, which has not been deleted. "//-
			"Para Compare Versions finds fewer differences, because it does not consider it to be a difference when "//-
			"a completely empty object is deleted. For example when empty table rows are deleted.\n\n"//-
			"All selected attributes are compared. If the newer and older attribute are empty, nothing is shown. "//-
	        "If the same version of the same module is selected all attributes are exported. "//-
			"Only the attribute text is compared, but not the rich text format and OLE objects.\n\n"//-
	sNormal "Some of the settings which influence the Para Version Compare output are stored in the registry.\n\n"//-
	sHeading "Basic Steps to Perform a Comparison\n\n"//-
	sIndent "Select a different older module by clicking on \\b Browse Older\\b0. (Optional)\n"//-
			"Select the versions to compare in \\b Newer Version\\b0  and \\b Older Version\\b0.\n"//-
	        "Load the versions to get all attributes by clicking on \\b Load Versions\\b0. (Optional)\n"//-
	        "Select the attributes to compare. (Optional)\n"//-
	        "Select the output file by clicking on \\b Browse...\\b0. (Optional)\n"//-
	        "Click \\b Compare\\b0 .\n\n "//-
	sHeading "Attributes to Compare\n\n"//-
	sNormal "When Para Compare Versions is launched, the attributes of the current version are shown. "//-
	        "To get additional attributes from the compared versions, the compared versions have to be loaded. "//-
	        "Additional attributes are selected when they are added to the list.\n\n"//-
	        "All attributes can be selected by clicking on \\b Select All\\b0  or deselected by clicking on \\b Deselect All\\b0. "//-
	        "Individual attributes can be selected or deselected by clicking on them. "//-
	        "The attributes in the current view in the newer module can be selected by clicking on \\b As View\\b0 .\n\n"//-
	        "Attribute sets can be selected according to regular expressions by clicking on \\b Select\\b0 . "//-
	        "The regular expressions can be set according to the selected attributes in the \\b Attributes to Compare\\b0  list by clicking on \\b Set\\b0 ."
	
	DB  dbHelp = create ("Para Compare Versions Help", styleCentered)
	DBE rtbHelp = richText(dbHelp, "", sHelpText, 700, 500, true)
	show dbHelp
}

// *************************************************************************************************************************
// Attribute handling

// Update the selected field and buttons
void doUpdateSelectedCount (DBE dbe)
{
    int iTotalAttr = noElems (mlstAttrNames)
    int iSelectedAttr = 0
    string sAttrName = ""
    for sAttrName in mlstAttrNames do iSelectedAttr++
    
    if (iSelectedAttr == 0 ) 
    {
    	inactive btnDeselectAllAttr
    	inactive btnCompare
    }
    else {
    	active btnDeselectAllAttr
    	active btnCompare
    }
    
    if (iSelectedAttr == iTotalAttr) inactive btnSelectAllAttr
    else active btnSelectAllAttr
    
    set (fldSelectedAttrCount, iSelectedAttr "/" iTotalAttr "")
}

// Select all attributes of the list
void doSelectAllAttr (DBE dbe)
{ 
    int iAttrIndex = 0
    for iAttrIndex in 0 : (noElems mlstAttrNames)-1 do set (mlstAttrNames, iAttrIndex, true)
    doUpdateSelectedCount mlstAttrNames
}

// Deselect all attributes of the list
void doDeselectAllAttr (DBE dbe)
{ 
    int iAttrIndex
    for iAttrIndex in 0 : (noElems mlstAttrNames)-1 do set(mlstAttrNames, iAttrIndex, false)
    doUpdateSelectedCount mlstAttrNames
}

// Deselect all attributes of the list
void doSelectAsView (DBE dbe)
{ 
	Skip skAttrInView = create
	Column cColumn = null
	int iAttrInView = 0
	
	doDeselectAllAttr (DBE dbe)
	for cColumn in mNewerModule do 
	{
		if (main cColumn) 
		{
			put (skAttrInView, iAttrInView++, "Object Heading")
			put (skAttrInView, iAttrInView++, "Object Text")
		}	
		if (!null attrName cColumn) put (skAttrInView, iAttrInView++, attrName cColumn)
	}
    int iAttrIndex = 0
    for iAttrIndex in 0 : (noElems mlstAttrNames)-1 do 
    {
    	string sAttrName = get (mlstAttrNames, iAttrIndex)
    	string sAttrInView = ""
    	for sAttrInView in skAttrInView do 
    	{
    		if (sAttrInView ==  sAttrName) set (mlstAttrNames, iAttrIndex, true)
    	}
    }
    delete skAttrInView
    doUpdateSelectedCount mlstAttrNames
}

// Convert string into a regular expression, and catch any errors.
Regexp regexpOf (string sRegexPattern)
{ 
    noError
    Regexp isPatt = regexp sRegexPattern
    string errmsg = lastError
    if (errmsg != "") 
    {
        if (matches("<[^>]*> *", errmsg)) errmsg = errmsg[end(0)+1:]
        ack errmsg
        return null
    }
    return isPatt
}

// Select the attributes, which match the regular expression.
void doSelectMatchingAttr (DBE dbeCalling)
{
    string sRegexPattern = ""   
    if(dbeCalling == btnSelectMatchingAttr1) 
    {
    	sRegexPattern = get (fldRegexPatternAttr1)
		setRegistry (sRegistryPath, "Regex Pattern 1", sRegexPattern)
    }
    if(dbeCalling == btnSelectMatchingAttr2) 
    {
    	sRegexPattern = get (fldRegexPatternAttr2)
		setRegistry (sRegistryPath, "Regex Pattern 2", sRegexPattern)
    }
    if(dbeCalling == btnSelectMatchingAttr3) 
    {
    	sRegexPattern = get (fldRegexPatternAttr3)
		setRegistry (sRegistryPath, "Regex Pattern 3", sRegexPattern)
    }
    Regexp isPatt = regexpOf sRegexPattern
    if (null isPatt) return
    
    int iAttrIndex = 0
    for iAttrIndex in 0 : (noElems mlstAttrNames)-1 do
    {
        string sAttrName = get (mlstAttrNames, iAttrIndex)
        set (mlstAttrNames, iAttrIndex, isPatt sAttrName)
    }
    doUpdateSelectedCount mlstAttrNames
}

// Select the attributes according to the regex. 
void doSetMatchingAttr (DBE dbeCalling)
{
	string sRegexPattern = ""
	string sAttrName = ""
	for sAttrName in mlstAttrNames do
	{
		if (sRegexPattern != "") sRegexPattern = sRegexPattern "|"
		sRegexPattern = sRegexPattern "(^" sAttrName "$)"
	}
    if(dbeCalling == btnSetMatchingAttr1) 
    {
    	set (fldRegexPatternAttr1, sRegexPattern)
		setRegistry (sRegistryPath, "Regex Pattern 1", sRegexPattern)
    }
    if(dbeCalling == btnSetMatchingAttr2)
    {
    	set (fldRegexPatternAttr2, sRegexPattern)
    	setRegistry (sRegistryPath, "Regex Pattern 2", sRegexPattern)
    }
    if(dbeCalling == btnSetMatchingAttr3)
    {
    	set (fldRegexPatternAttr3, sRegexPattern)
    	setRegistry (sRegistryPath, "Regex Pattern 3", sRegexPattern)
    }
}

// Add elements to a sorted list if the element is not listed.
void addAttrToList (DBE dbeAttrList, string sNewAttrName)
{
	int iAttrPosition	// 3 to skip Object Heading, Object Short Text, and Object Text
	for (iAttrPosition = 3; iAttrPosition < noElems dbeAttrList; iAttrPosition++)
	{
		string sAttrName = get (dbeAttrList, iAttrPosition)
		if (sAttrName == sNewAttrName) return // exists already
		if (sAttrName > sNewAttrName) break	// insert here
	}
	insert (dbeAttrList, iAttrPosition, sNewAttrName)
	set (dbeAttrList, iAttrPosition, true)
}

// Fill the attributes list with module attributes.
void addVersionAttibutesToList (Module mModule)
{
	AttrDef adAttrDefinition = null
	for adAttrDefinition in mModule do
	{
		if (adAttrDefinition.module || adAttrDefinition.system) continue
		addAttrToList (mlstAttrNames, adAttrDefinition.name "")
	}
}

// *************************************************************************************************************************
// Module version handling

// Make sure that the newer version is really newer
void checkVersionSelection (DBE dbeKlick)
{
	if(mOlderModule == mNewerModule)
	{
		int iNewerIndex = get lstNewer            // position in list
		int iOlderIndex = get lstOlder
		
		if (dbeKlick == lstOlder && iNewerIndex > iOlderIndex)  set (lstNewer, iOlderIndex)
		if (dbeKlick == lstNewer && iNewerIndex > iOlderIndex)  set (lstOlder, iNewerIndex)
	}
}

// fill a version list
void fillVersionList (DBE lstVersion, Module mVersion)
{
	Baseline bBaseline = null
	empty lstVersion
	for bBaseline in mVersion do
	{
		string sBaseline = major bBaseline "." minor bBaseline "  " suffix bBaseline
		insert (lstVersion, 0, sBaseline)
	}
	Date dNow = dateOf intOf today 
	string sNow = dNow ""
	insert (lstVersion, 0, "Current " today sNow[8:])	// add the current version
	set (lstVersion, 0)
}

// Show information about the selected baselines.
void showVersionInformation (Module mModule, string sNewerBaselineName, string sOlderBaselineName)
{
	string sMarkup = ""
															// show baseline information
	strmOutput << sBlack "\\pard \\fs24\\ul Versions in " fullName mModule "\\ul0 \\par\\par\n"
	if (bJustExport) strmOutput << "\\fs16 The exported version is shown black.\\par\n"
	else strmOutput << "\\fs16 Compared versions are shown black.\\par\n"
	strmOutput << "\\fs16 Other versions are shown " sGray "gray" sBlack ".\\par\\par\n"
	strmOutput << "\\trowd \\trgaph108\\trleft-108\\trhdr"
	strmOutput << sAllBorders "\\cellx2500 " sAllBorders "\\cellx4000 " sAllBorders "\\cellx5700 " sAllBorders "\\cellx10000 \n"
	strmOutput << "\\intbl \\fs16\\b Version \\cell Created by \\cell Date \\cell Annotation \\cell\\b0\\row\n"
	Baseline bBaseline = null
	for bBaseline in mModule do
	{
		string sBaseline = major bBaseline "." minor bBaseline "  " suffix bBaseline
		if (sNewerBaselineName == sBaseline || sOlderBaselineName == sBaseline) sMarkup = sBlack
		else sMarkup = sGray
		strmOutput << "\\intbl " sMarkup sBaseline "\\cell " user bBaseline "\\cell " dateOf bBaseline "\\cell " annotation bBaseline "\\cell\\row\n"
	}
	string sCurrentVersion = ""
	sMarkup = sGray
	if (fullName mModule == sNewerModuleName)
	{
		if (get lstNewer == 0) sMarkup = sBlack
		sCurrentVersion = get (lstNewer, 0)
	}
	if (fullName mModule == sOlderModuleName)
	{
		if (get lstOlder == 0) sMarkup = sBlack
		sCurrentVersion = get (lstOlder, 0)
	}
	strmOutput << "\\intbl " sMarkup sCurrentVersion "\\cell "  "\\cell "  "\\cell "  "\\cell\\row\n"
}

// Show information about the modules and selected attributes.
void showModuleInformation (Module mNewerVersion, Module mOlderVersion)
{
	string sNewerVersionName = get lstNewer            // version name
	string sOlderVersionName = get lstOlder
	string sMarkup = ""
	
	if (fullName mNewerVersion == fullName mOlderVersion) 
	{
		showVersionInformation (mNewerVersion, sNewerVersionName, sOlderVersionName)
	}
	else
	{
		showVersionInformation (mNewerVersion, sNewerVersionName, sNewerVersionName)
		strmOutput << sBlack "\\pard\\par\\par\n"
		showVersionInformation (mOlderVersion, sOlderVersionName, sOlderVersionName)
	}

	// show compared attributes
	if (bJustExport)
	{
		strmOutput << sBlack "\\pard\\par \\fs24 \\ul Exported Attributes\\ul0 \\par \\par\n"
		strmOutput << "\\fs16 Exported attributes are shown black.\\par \n"
	}
	else
	{
		strmOutput << sBlack "\\pard\\par \\fs24 \\ul Compared Attributes\\ul0 \\par \\par\n"
		strmOutput << "\\fs16 Compared attributes are shown black.\\par \n"
	}
	strmOutput << "\\fs16 Other existing attributes are shown " sGray "gray" sBlack ".\\par\\par\n"
	strmOutput << "\\trowd \\trgaph108\\trleft-108\\trhdr"
	strmOutput << sAllBorders "\\cellx5000 " sAllBorders "\\cellx10000\n"
	strmOutput << "\\intbl \\fs16\\b " sOlderModuleName "\\par " sOlderVersionName "\\cell " sNewerModuleName "\\par " sNewerVersionName "\\b0\\cell\\row\n"
	string sAttrName = ""
	string sNewerAttr = ""
	string sOlderAttr = ""
	int iAttributesCount
	for (iAttributesCount = 0; iAttributesCount < noElems mlstAttrNames; iAttributesCount++)
	{
		sAttrName = get (mlstAttrNames, iAttributesCount)
		current = mNewerVersion
		if (exists attribute sAttrName) sNewerAttr = sAttrName
		else sNewerAttr = ""
		current = mOlderVersion
		if (exists attribute sAttrName) sOlderAttr = sAttrName
		else sOlderAttr = ""
		if (sOlderAttr != "" || sNewerAttr != "")
		{
			sMarkup = sGray
			if (selected (mlstAttrNames, iAttributesCount)) sMarkup = sBlack 
			strmOutput << "\\intbl " sMarkup sOlderAttr "\\cell " sNewerAttr "\\cell\\row\n"
		}
	}
}

// Load the versions and update the attributes to compare
Module loadVersion (DBE lstVersion, Module mModule)
{
	Module mVersion = null
	Baseline bBaseline = null
	string sVersionName = get lstVersion            // version name
	string sModuleName = fullName mModule
	
	progressStart (dbCompareVersions, " Para Compare Versions", "", 1)
	progressMessage "Loading " sModuleName " " sVersionName "..."

	if (get lstVersion == 0) mVersion = read (sModuleName, false)	// current version    
	else	// look for the baseline and load it
	{
		for bBaseline in mModule do
		{
			string sBaselineName = major bBaseline "." minor bBaseline "  " suffix bBaseline
			if (sVersionName == sBaselineName) mVersion = load (mModule, bBaseline, false)
		}
	}
	progressStop
	return mVersion
}

// Load the versions and update the attributes to compare
void doLoadVersions (DBE btnLoadVersions)
{
	mNewerVersion = loadVersion (lstNewer, mNewerModule)
	current = mNewerVersion
	filtering off
	sorting off
	addVersionAttibutesToList mNewerVersion

	mOlderVersion = loadVersion (lstOlder, mOlderModule)
	current = mOlderVersion
	filtering off
	sorting off
	addVersionAttibutesToList mOlderVersion

    doUpdateSelectedCount mlstAttrNames
}

// Browse the selected project
void doBrowseOlder (DBE x){
    Item thisItem = item sOlderModuleName
	Folder fParentFolder = getParentFolder mOlderModule
    string sSelectedOlderModule = fnMiniExplorer (dbCompareVersions, fParentFolder, MINI_EXP_FORMAL_MODS, "Para Compare Versions Browse Older Module", "Select an older module...")

    thisItem = item sSelectedOlderModule
    if (!null thisItem) 
    {
        if (type thisItem == "Formal") 
        {
            sOlderModuleName = fullName thisItem
			mOlderModule = read (sOlderModuleName, false)    
			fillVersionList (lstOlder, mOlderModule)
			set (fldOlderVersion, sOlderModuleName)
        } 
    }
}

// *************************************************************************************************************************
// Comparison Engine

// Cleanup and untagle the RTF string returned by diff
string fixRTFragment (string sMess)
{
	int i = 0					 
	int iLenght = length sMess

	// remove the additional line break 
	string sClosingBraket = ""
	if(iLenght > 6 && sMess[iLenght-6:iLenght-1] == "\\par }")  
	{
		iLenght = iLenght - 6	// remove the additional line break
		sClosingBraket = "}"	// remeber that the closing braket has been removed and add it later
	}

	// clean up the RTF string by combining words of the same RTF format
	string sClean = ""				
	bool bInFormat = false
	string sFormat = ""
	string sLastFormat = ""
	for (i=0; i < iLenght; i++)
	{
		if (sMess[i] == '{') bInFormat = true	// format start
		if (bInFormat) sFormat = sFormat sMess[i] ""  // get the formatting
		if(sMess[i] == ' ' && bInFormat) // format finished 
		{
			if(sFormat != sLastFormat) // new format
			{
				if(sLastFormat != "") sClean = sClean "}"  // close old format
				sClean = sClean sFormat	// add the new format
			}
			sLastFormat = sFormat
			sFormat = ""
			bInFormat = false
		}
		else if(!bInFormat)
		{
			if (sMess[i] != '}') sClean = sClean sMess[i] ""	// only if not from the same format
			else if (sMess[i+1] != '{') 	// end of format and black text is following
			{
				sClean = sClean sMess[i] ""
				sLastFormat = ""
			}
		}
	}
	sClean = sClean sClosingBraket

	// untangle the mixed removed / added words
	string sUntangled = ""
	bool bInRemoved = false
	bool bInAdded = false
	bool bInUnchanged = true
	int iRemoved = 0
	int iAdded = 0
	string sRemovedText = ""
	string sAddedText = ""
	string sUnchangedText = ""
	string sSpacer = ""
	string sRemovedFragment = ""

	for (i=0; i < length sClean; i++)
	{
		if (sClean[i] == '{') bInRemoved = bInAdded = bInUnchanged = false   // start of another format
		if (bInRemoved)
		{
			if (sClean[i] != '}') sRemovedFragment = sRemovedFragment sClean[i] ""  // collect removed characters
			else  
			{	// check if there is a space missing
				if (sRemovedText != "" && sRemovedFragment[0] != ' ') sRemovedText = sRemovedText " "
				sRemovedText = sRemovedText sRemovedFragment
				sRemovedFragment = ""
			}
		}

		if (sClean[i] == sRemovedStyle[iRemoved]) iRemoved++   // find the start of removed text
		else iRemoved = 0
		if (iRemoved == length sRemovedStyle) bInRemoved = true
		
		if (bInAdded && sClean[i] != '}') sAddedText = sAddedText sClean[i] ""  // collect added characters

		if (sClean[i] == sAddedStyle[iAdded]) iAdded++   // find the start of added text
		else iAdded = 0
		if (iAdded == length sAddedStyle) bInAdded = true
		
		if (bInUnchanged) sUnchangedText = sUnchangedText sClean[i] ""	// collect unchanged characters

		if (sClean[i] == '}')  // the end of a format - check if the marked up text has been untangled
		{
			int x = i + 1					// read spaces to see what's next
			while (sClean[x] == ' ') x++

			if(sClean[x] != '{')  // unchanged text is following - add collected text to the untangled text
			{
				if (sRemovedText != "") 
				{
					if (sRemovedText[0] == ' ')	sRemovedText = " {" sRemovedStyle sRemovedText[1:] "}"
					else sRemovedText = "{" sRemovedStyle sRemovedText "}"
				}
				if (sRemovedText != "" && sAddedText != "") sSpacer = " " // add space between removed and added text
				else sSpacer = ""
				if (sAddedText != "") sAddedText = "{" sAddedStyle sAddedText "}" 
				sUntangled = sUntangled sUnchangedText sRemovedText sSpacer sAddedText
				sUnchangedText = sRemovedText = sAddedText = ""
				bInUnchanged = true
				bInRemoved = bInAdded = false
			}
		}
	}
	sUntangled = sUntangled sUnchangedText  // just in case if there is unchanged text at the end
	return sUntangled
}

Buffer buffNewerAttr = null
Buffer buffOlderAttr = null
Buffer buffAttrDifference = null
// Compare an attribute of two objects with same absolute number or agaist a null object.
bool compareObjects (Object oNewer, Object oOlder, string sAttrName)
{
	string sObjectId = ""
	string sObjectNumber = ""
	string sNewerAttr = ""
	string sOlderAttr = ""
	bool bAttrChanged = false
	bool bShowMainColumn = false
	bool bDifferentBorder = false
	string sRTFragment = ""
	string sChangeIndicator = "   "

	sNewerAttr = probeAttribute_(oNewer, sAttrName, false)	// get newer attribute if it exists
	sOlderAttr = probeAttribute_(oOlder, sAttrName, false)	// get older attribute if it exists
	if (!null sNewerAttr) 
	{
		buffNewerAttr = sNewerAttr
		bShowMainColumn = (bShowAllMainAttributes && (sAttrName == "Object Text" || sAttrName == "Object Heading") && length sNewerAttr > 0)
	}
	else buffNewerAttr = ""
	if (!null sOlderAttr) buffOlderAttr = sOlderAttr
	else buffOlderAttr = ""
	
	if (!bJustExport && buffNewerAttr != buffOlderAttr) 
	{
		bAttrChanged = true	// attribute changed
		if (null sNewerAttr) sChangeIndicator = sRemovedBack "rm" sWhiteBack
		else if (null sOlderAttr) sChangeIndicator = sAddedBack "ad" sWhiteBack
		else sChangeIndicator = sRemovedBack "c" sAddedBack "h" sWhiteBack
	}
	
	if (bAttrChanged || (bShowUnchangedAttributes && length buffNewerAttr > 0) || bShowMainColumn)
	{
		Object oConcerned = oNewer
		if (null oNewer) oConcerned = oOlder 
		sObjectId = identifier oConcerned		// get the identifer
		if (sAttrName == "Object Heading") sObjectNumber = sGreen number oConcerned sBlack " "
		string sTablePerfix = ""
		if (cell oConcerned) sTablePerfix = "Cell " 
		if (sObjectId == sPreviousObjectId) 
		{
			sObjectId = ""
			sIdentifierBorder = sLeftBorder
		}
		else 
		{
			progressMessage "Outputting object " sObjectId 
			sPreviousObjectId = sObjectId
			sIdentifierBorder = sTopBorder sLeftBorder
		}
		
		bDifferentBorder = (sOldIdentifierBorder != sIdentifierBorder) || sOldIdentifierBorder == ""
		if (bDifferentBorder) strmOutput << "\\trowd \\trgaph108\\trleft-108\\trkeep"

		sRTFragment = ""
		if(length buffNewerAttr < iBloodyBig && length buffOlderAttr < iBloodyBig)
		{
			if (bAttrChanged) 
			{
				if (hasWhiteSpace buffOlderAttr && hasWhiteSpace buffNewerAttr)
				{
					diff (buffAttrDifference, buffOlderAttr, buffNewerAttr, sRemovedStyle, sAddedStyle)
					findColorTable buffAttrDifference	// find the color table with a regex 
					sRTFragment = fixRTFragment (buffAttrDifference[end 0 + 1:(length buffAttrDifference) - 3])
				}
				else 
				{
					if (length buffOlderAttr > 0) sRTFragment = "{" sRemovedStyle exportRTFString buffOlderAttr "} " 
					if (length buffNewerAttr > 0) sRTFragment = sRTFragment "{" sAddedStyle exportRTFString buffNewerAttr "}"
				}
			}
			else
			{
				if(bJustExport) 
				{
					sRTFragment = exportRTFString richTextFragment probeRichAttr_(oNewer, sAttrName, false)
					bAttrChanged = true // count also exported attributes
				}
				else sRTFragment = exportRTFString sNewerAttr
			} 
		}
		else sRTFragment = sCyan "This attribute is too long. Figure out the difference yourself..."

		if(bDifferentBorder) strmOutput << sIdentifierBorder "\\cellx1426 " sTopBorder "\\cellx3127 " sTopBorder "\\cellx3527 " sTextBorder "\\cellx10031\n"
		if (sAttrName == "Object Heading") strmOutput << "\\intbl \\b " sGreen sObjectId "\\cell " sTablePerfix sAttrName "\\cell "sChangeIndicator "\\cell " sObjectNumber sRTFragment "\\b0\\cell\\row\n"
		else strmOutput << "\\intbl "  sGreen sObjectId "\\cell " sTablePerfix sAttrName  "\\cell "sChangeIndicator sBlack "\\cell " sRTFragment "\\cell\\row\n"

		sOldIdentifierBorder = sIdentifierBorder
	}
	return bAttrChanged
}

// Compare the two versions
void doCompareVersions (DBE dbe)
{
	string sNewerVersionName = get lstNewer            // version name
	string sOlderVersionName = get lstOlder
	string sOutputFileName = get fnOutputFileBrowse
	string sTemp = ""

	buffNewerAttr = create iBloodyBig
	buffOlderAttr = create iBloodyBig
	buffAttrDifference = create iBloodyBig

	bShowUnchangedAttributes = get cbShowUnchangedAttributes
	if (bShowUnchangedAttributes) sTemp = "true"
	else sTemp = "false" 
	setRegistry (sRegistryPath, "Show Unchanged Attributes", sTemp)
	
	bShowAllMainAttributes = get cbShowAllMainAttributes
	if (bShowAllMainAttributes) 
	{
		sTemp = "true"
		set (mlstAttrNames, 0, true)	// select Object Heading and Text attributes
		set (mlstAttrNames, 2, true)
	}
	else sTemp = "false" 
	setRegistry (sRegistryPath, "Show All Main Attributes", sTemp)

	setRegistry (sRegistryPath, "Newer Color", get chcNewerColor)
	sAddedBack = saColorBacks[get chcNewerColor]
	if (get cbUnderlined) {
		sTemp = "true"
		sAddedStyle = "\\ul" saColorFonts[get chcNewerColor]
	}
	else {
		sTemp = "false"
		sAddedStyle = saColorFonts[get chcNewerColor]
	} 
	setRegistry (sRegistryPath, "Underlined", sTemp)
	
	setRegistry (sRegistryPath, "Older Color", get chcOlderColor)
	sRemovedBack =  saColorBacks[get chcOlderColor]
	if (get cbStrikeThrough) {
		sTemp = "true"
		sRemovedStyle = "\\strike" saColorFonts[get chcOlderColor]
	}
	else {
		sTemp = "false"
		sRemovedStyle =  saColorFonts[get chcOlderColor]
	} 
	setRegistry (sRegistryPath, "Strike Through", sTemp)
	
	
	bJustExport = fullName mNewerModule == fullName mOlderModule && sNewerVersionName == sOlderVersionName
	if(bJustExport)
	{	// allways use these settings for the export
		bShowUnchangedAttributes = true
	}
	
	string sEndMessage = "The End"
	sOldIdentifierBorder = ""

	int iCount = 0
	string sOutputFilePath = ""
	for (iCount = 0; iCount < length sOutputFileName; iCount++)
	{
		if (sOutputFileName[iCount] == '\\') sOutputFilePath = sOutputFileName[0:iCount]
	} 
	setRegistry (sRegistryPath, "Output File Path", sOutputFilePath)

	noError
	strmOutput = write sOutputFileName
	string sOpenStatus = lastError
	if (!null sOpenStatus)
	{
		matches(".> ", sOpenStatus)
		ack sOpenStatus[(end 0)+1:] "\nMake sure that the path exists and that no other pogram is blocking the output file."
		return
	}
	
	// file header
	strmOutput << "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fswiss\\fcharset0 Times New Roman;}}" sColorTable "\n"
	strmOutput << "\\paperw11906\\paperh16838\\margl1134\\margr567\\margt1134\\margb851\\headery567\\footery567\n"
	strmOutput << "\\b \\ul \\fs24 Para Compare Versions\\ul0 \\b0 \\par\\par\n"

	doLoadVersions btnLoadVersions
	showModuleInformation (mNewerVersion, mOlderVersion)

	int iObjectCount = 0
	int iNewerObjects = 0
	int iRemovedObjects = 0
	int iAddedObjects = 0
	int iDifferences = 0
	int iComparedAttr = 0
	
	if (bJustExport)
	{
		strmOutput << "\\pard\\par\\fs24\\ul\\cf0 Exported Objects\\ul0\\par\\par\n"//-
		              "\\fs16 The attributes are exported in rich text format.\\par\\par\n"
	}
	else 
	{
		strmOutput << "\\pard\\par\\fs24\\ul\\cf0 Compared Objects\\ul0\\par\\par\n"//-
		              "\\fs16 Information added by Para Compare Versions is shown " sGreen "green" sBlack ".\\par\n"
	    strmOutput <<  "Information only in " sOlderModuleName " " sOlderVersionName " is shown " sRemovedStyle "like this" sBlack "\\strike0\\ul0.\\par "//-
                       "Information only in " sNewerModuleName " " sNewerVersionName " is shown " sAddedStyle "like this" sBlack "\\strike0\\ul0.\\par "//-
					   "Information in both modules is shown black.\\par\\par\n"
	} 
	strmOutput << "\\trowd \\trgaph108\\trleft-108\\trhdr"
	strmOutput << sAllBorders "\\cellx1426 " sAllBorders "\\cellx3127 " sAllBorders "\\cellx3527 " sAllBorders "\\cellx10031\n"
	strmOutput << "\\intbl \\b " sGreen "Identifier\\cell Attribute Name\\cell S\\cell Attribute Text\\b0 \\cell\\row\n"
	
	Object oNewer = null
	Object oOlder = null
	string sAttrName = ""
	
	Skip skRemoved = create
	int iLastAbsoluteNumber = -1	
	for oOlder in entire mOlderVersion do	// list older objects not in newer module with the according predecessor
	{
		if (isDeleted oOlder || table oOlder || row oOlder) continue
		
		int iOlderAbsoluteNumber = oOlder."Absolute Number"
		oNewer = object (iOlderAbsoluteNumber, mNewerVersion)
		if (null oNewer || isDeleted oNewer)
		{
			if (iLastAbsoluteNumber == -1) // output first deleted objects
			{
				for sAttrName in mlstAttrNames do
				{
					if (compareObjects (oNewer, oOlder, sAttrName)) iDifferences++
					iComparedAttr++
				}
				iObjectCount++
			}
			else put (skRemoved, iOlderAbsoluteNumber, iLastAbsoluteNumber)
			iRemovedObjects++
		}
		else iLastAbsoluteNumber = iOlderAbsoluteNumber
	}
	for oNewer in entire mNewerVersion do
	{
		if (isDeleted oNewer || table oNewer || row oNewer) continue
		iNewerObjects++
	}
	
	progressStart (dbCompareVersions, " Para Compare Versions", "", iNewerObjects + iRemovedObjects)
	int iStartTime = intOf today
	for oNewer in entire mNewerVersion do	// sorted according to the order in the newer version
	{
		if (isDeleted oNewer || table oNewer || row oNewer) continue
		
		progressStep iObjectCount++
		int iNewerAbsoluteNumber = oNewer."Absolute Number"
		oOlder = object (iNewerAbsoluteNumber, mOlderVersion)
		if (null oOlder || isDeleted oOlder || table oOlder || row oOlder) iAddedObjects++
		
		for sAttrName in mlstAttrNames do	// compare the newer object against an older one
		{
			if (compareObjects (oNewer, oOlder, sAttrName)) iDifferences++
			iComparedAttr++
		}

		int iAbsoluteNumber		// inserte deleted older objects after the previous newer one
		for iAbsoluteNumber in skRemoved do
		{
			if (iAbsoluteNumber == iNewerAbsoluteNumber)
			{
				progressStep iObjectCount++
				int iOlderAbsoluteNumber = key skRemoved
				oOlder = object (iOlderAbsoluteNumber, mOlderVersion)
				oNewer = null
				for sAttrName in mlstAttrNames do
				{
					if (compareObjects (oNewer, oOlder, sAttrName)) iDifferences++
					iComparedAttr++
				}
			}
		}
		
		if (progressCancelled) 
		{
			sEndMessage = "Canceled"
			break
		}
	}
	strmOutput << "\\trowd \\trgaph108\\trleft-108\\trkeep"
	strmOutput << sAllBorders "\\cellx1426 " sAllBorders "\\cellx3127 " sAllBorders "\\cellx3527 " sAllBorders "\\cellx10031\n"
	strmOutput << "\\intbl \\b " sGreen sEndMessage "\\cell " sEndMessage "\\cell\\cell " sEndMessage sBlack "\\b0\\cell\\row\n"
	progressStop

	if (bJustExport) strmOutput << "\\pard \\fs24 \\par Checked " iComparedAttr " attributes and exported " iDifferences ".\\par \n"
	else strmOutput << "\\pard \\fs24 \\par Compared " iComparedAttr " attributes and found " iDifferences " differences.\\par \n"
	strmOutput << iObjectCount " objects containing " iAddedObjects " added and " iRemovedObjects " removed objects.\\par }\n"

	close strmOutput
	delete buffNewerAttr
	delete buffOlderAttr
	delete buffAttrDifference
	delete skRemoved
	current = mNewerModule	// return to current version

	int iEndTime = intOf today
	int iSeconds = iEndTime - iStartTime
	if (iSeconds == 0) iSeconds = 1
	int iAttrPerSecond = iComparedAttr/iSeconds
	if (bJustExport) infoBox "Checked " iComparedAttr " attributes and exported " iDifferences " in " iSeconds " seconds.\n" iAttrPerSecond " attributes per second."
	else infoBox "Found " iDifferences " differences.\nCompared " iComparedAttr " attributes in " iSeconds " seconds.\n" iAttrPerSecond " comparisions per second."
}

// *************************************************************************************************************************
// Main program
bool bTemp = false
string sTemp = ""
int iTemp = 0

Module mCurrent = current
if (null mCurrent)
{
	ack "Start Para Compare Versions from a module..."
	halt
}

mNewerModule = mOlderModule = mCurrent
sNewerModuleName = fullName mNewerModule
sOlderModuleName = fullName mOlderModule

// Dialog for selecting two baselines for comparison
int iHalfWidth = 275
string strEmptyArr[] = {}

dbCompareVersions = create ("Para Compare Versions", styleCentered|styleFixed)

lstNewer = list (dbCompareVersions, "Newer Version", iHalfWidth, 9, strEmptyArr)
lstNewer->"left"->"form"
lstNewer->"right"->"unattached"
fldNewerVersion = field (dbCompareVersions, "", "Not loaded yet...", 42, true)
fldNewerVersion->"left"->"form"

lstOlder = list(dbCompareVersions, "Older Version", iHalfWidth, 9, strEmptyArr)
lstOlder->"left"->"form"
lstOlder->"right"->"unattached"
fldOlderVersion = field (dbCompareVersions, "", "Not loaded yet...", 42, true)
fldOlderVersion->"left"->"form"

mlstAttrNames = multiList (dbCompareVersions, "Attributes to Compare", iHalfWidth, 24, strEmptyArr)
mlstAttrNames->"top"->"form"
mlstAttrNames->"left"->"flush"->lstNewer
mlstAttrNames->"right"->"unattached"

btnLoadVersions = button (dbCompareVersions, "Load Versions", doLoadVersions)
btnLoadVersions->"top"->"spaced"->fldOlderVersion
beside dbCompareVersions
btnCompare = button (dbCompareVersions, "Compare", doCompareVersions)
btnBrowseOlder = button(dbCompareVersions, " Browse Older ", doBrowseOlder, false)

btnSelectAllAttr = button (dbCompareVersions, "Select All", doSelectAllAttr)
btnSelectAllAttr->"left"->"flush"->lstNewer
btnDeselectAllAttr = button (dbCompareVersions, "Deselect All", doDeselectAllAttr)
btnSelectAsView = button (dbCompareVersions, "As View", doSelectAsView)
fldSelectedAttrCount = field (dbCompareVersions, "", "", 4, true)
fldSelectedAttrCount->"right"->"form"

left dbCompareVersions
bTemp = getRegistry (sRegistryPath, "Show Unchanged Attributes") == "true"
cbShowUnchangedAttributes = toggle (dbCompareVersions,"Show unchanged attributes", bTemp)
cbShowUnchangedAttributes->"top"->"spaced"->btnLoadVersions
beside dbCompareVersions
bTemp = getRegistry (sRegistryPath, "Show All Main Attributes") == "true"
cbShowAllMainAttributes = toggle (dbCompareVersions,"Show all main attributes", bTemp)

left dbCompareVersions
sTemp = getRegistry (sRegistryPath, "Newer Color")
if (!null sTemp) iTemp = intOf sTemp
else iTemp = 3
chcNewerColor = choice (dbCompareVersions, "Text only in newer version", saColors, iTemp, 5, false)
beside dbCompareVersions
bTemp = getRegistry (sRegistryPath, "Underlined") == "true"
cbUnderlined = toggle (dbCompareVersions,"Underlined", bTemp)

sTemp = getRegistry (sRegistryPath, "Older Color")
if (!null sTemp) iTemp = intOf sTemp
else iTemp = 2
chcOlderColor = choice (dbCompareVersions, "Text only in older version", saColors, iTemp, 5, false)
chcOlderColor->"left"->"flush"->lstNewer
beside dbCompareVersions
bTemp = getRegistry (sRegistryPath, "Strike Through") == "true"
cbStrikeThrough = toggle (dbCompareVersions,"Strike through", bTemp)

left dbCompareVersions
btnSelectMatchingAttr1 = button (dbCompareVersions, "Select", doSelectMatchingAttr)
beside dbCompareVersions
btnSetMatchingAttr1 = button(dbCompareVersions, "Set", doSetMatchingAttr)
sTemp = getRegistry (sRegistryPath, "Regex Pattern 1")
if (null sTemp) sTemp = "(^Object Heading$)|(^Object Text$)"
fldRegexPatternAttr1 = field (dbCompareVersions, "", sTemp, 1)
fldRegexPatternAttr1->"right"->"form"

left dbCompareVersions
btnSelectMatchingAttr2 = button (dbCompareVersions, "Select", doSelectMatchingAttr)
beside dbCompareVersions
btnSetMatchingAttr2 = button (dbCompareVersions, "Set", doSetMatchingAttr)
sTemp = getRegistry (sRegistryPath, "Regex Pattern 2")
if (null sTemp) sTemp = "Enter a regular expression to match attributes here, see example above."
fldRegexPatternAttr2 = field (dbCompareVersions, "", sTemp, 1)
fldRegexPatternAttr2->"right"->"form"

left dbCompareVersions
btnSelectMatchingAttr3 = button (dbCompareVersions, "Select", doSelectMatchingAttr)
beside dbCompareVersions
btnSetMatchingAttr3 = button (dbCompareVersions, "Set", doSetMatchingAttr)
sTemp = getRegistry (sRegistryPath, "Regex Pattern 3")
if (null sTemp) sTemp = "Select matches the attributes; Set gets the expression from the attributes to compare."
fldRegexPatternAttr3 = field (dbCompareVersions, "", sTemp, 1)
fldRegexPatternAttr3->"right"->"form"

left dbCompareVersions
string sOutputFilePath = getRegistry (sRegistryPath, "Output File Path")
string sOutputFileName = sOutputFilePath "Para Compare Versions " mNewerModule."Name" ".rtf"
fnOutputFileBrowse = fileName (dbCompareVersions, "Output File ", sOutputFileName, "*.rtf", "Rich Text Files", false)
fnOutputFileBrowse->"left"->"form"

// dummy to get the copy right in the right place
left dbCompareVersions
DBE lblDummy = label (dbCompareVersions, "")
lblDummy->"top"->"spaced"->fnOutputFileBrowse
DBE lblCopyRight = label (dbCompareVersions, "Copyright (C) Reinhold Lauer 2011")
lblCopyRight->"bottom"->"spaced"->lblDummy
inactive (lblCopyRight)

apply (dbCompareVersions, "Help", applyShowHelp)
realize (dbCompareVersions, 0, 0)                     // realize so that the lists can be populated

fillVersionList (lstNewer, mNewerModule)
set (lstNewer, checkVersionSelection, checkVersionSelection)
set (fldNewerVersion, sNewerModuleName)

fillVersionList (lstOlder, mOlderModule)
set (lstOlder, checkVersionSelection, checkVersionSelection)
set (fldOlderVersion, sOlderModuleName)

// add the attributes to the list
insert (mlstAttrNames, 0, "Object Heading")
set (mlstAttrNames, 0, true)
insert (mlstAttrNames, 1, "Object Short Text")
set (mlstAttrNames, 1, true)
insert (mlstAttrNames, 2, "Object Text")
set (mlstAttrNames, 2, true)
addVersionAttibutesToList (mNewerModule)
doUpdateSelectedCount (mlstAttrNames)
set (mlstAttrNames, doUpdateSelectedCount, doUpdateSelectedCount)

show dbCompareVersions