//  Build Hierarchy after import from spreadsheet

/*
    Description:
    
	Moves objects into a proper hierarchy after import from
	spreadsheet.
	
	Assumes that the "main" column in the spreadsheet that contains
	object text is prefixed with a paragraph number for heading objects.
	
	The first object in the module must be a level one object.
	
	This paragraph number is parsed and used to determine the level of the
	object in the hierarchy.
	
	These paragraph numbers are removed and the remaining text is moved 
	into the Object Heading.
	
	smartdxl.com
*/

pragma runLim, 0

const string ATTR_LEVEL_INDICATOR = "Object Text"
const string ATTR_NEW_LEVEL       = "New Level"


/******************************************************************************
	createLevelAttr
******************************************************************************/
void createLevelAttr(Module m)
{
	AttrDef ad = null
	
	
	(current ModuleRef__) = m
	
	if (!exists attribute ATTR_NEW_LEVEL)
	{
		ad = create(object type "Integer" attribute ATTR_NEW_LEVEL)
	}
}


/******************************************************************************
	flattenHierarchy
******************************************************************************/
string flattenHierarchy(Module m)
{
	Object o       = null
	Object oFirst  = null
	Object oPrev   = null
	int    count   = 0
	Skip   objects = create
	
	oFirst = first(m)
	
	if (null oFirst || level(oFirst) != 1)
	{
		return("Module must begin with a level 1 object")
	}
	
	showExplorer(false)
	showTables(false)
	
	for o in m do
	{
		put(objects, count++, o)
	}
	
	
	for o in objects do
	{
		if (o == oFirst)
		{
			oPrev = o
		}
		else
		{
			//print("move " identifier(o) " after " identifier(oPrev) "\n")
			move(o, oPrev)
			
			oPrev = o
		}
	}
	
	delete(objects)
	return("")
}


/******************************************************************************
	getLevel
	
	Returns the proposed level of the object based on the contents of
	its Object Text. If the object text is empty or does not start with
	a digit then a level of zero is returned. Otherwise the level returned 
	is based on the number of digits separated by periods that are found
	at the beginnign of the object text.
******************************************************************************/
int getLevel(Object o)
{
	string s = ""
	string levelString = ""
	int len = 0
	int i = 0
	int depth = 0
	
	
	s = o.ATTR_LEVEL_INDICATOR ""
	
	len = length(s)
	
	// empty string, return zero
	if (len == 0)
	{
		return(0)
	}
	
	// does not start with a number, return zero
	if (!isdigit(s[0]))
	{
		return(0)
	}
	
	// extract the legal number from the beginning of the
	// string. Add an extra period at the end to make the next bit easier
	for (i = 0; i < len; i++)
	{
		if (s[i] != '.' && !isdigit(s[i]))
		{
			levelString = s[0:i-1] "."
			break
		}
	}
	
	// count the periods to determine the depth
	for (i = 0; i < length(levelString); i++)
	{
		if (levelString[i] == '.') depth++
	}
	
	return(depth)
}



/******************************************************************************
	getHeading
******************************************************************************/
string getHeading(string s)
{
	int len = 0
	int i = 0
	
	
	len = length(s)
	
	for (i = 0; i < len; i++)
	{
		if (isalpha(s[i]))
		{
			return(s[i:])
		}
	}
	
	return(s)
}


/******************************************************************************
	setInitialLevel
	
	populate level new level attribute based on contents of
    level indicator attribute (usually object text)
******************************************************************************/
void setInitialLevel(Module m)
{
	Object o = null
	
	for o in m do
	{
		o.ATTR_NEW_LEVEL = getLevel(o)
	}
}


/******************************************************************************
	refineLevels
	
	change new level attribute for objects with new level of zero.
	
	If new level attribute is zero then set it to level below previous object
	that had a non-zero level.
******************************************************************************/
void refineLevels(Module m)
{
	Object o = null
	int    prevLevel = 0
	int    thisLevel = 0
	
	for o in m do
	{
		thisLevel = o.ATTR_NEW_LEVEL
		
		if (thisLevel == 0)
		{
			o.ATTR_NEW_LEVEL = prevLevel + 1
		}
		else
		{
			o."Object Heading" = getHeading(o."Object Text" "")
			o."Object Text" = ""
			
			prevLevel = thisLevel
		}
	}
}


/******************************************************************************
	moveObjects
******************************************************************************/
void moveObjects(Object oParent)
{
	
}


/******************************************************************************
	moveObjects
******************************************************************************/
void moveObjects(Module m)
{
	Object o = null
	Object oParent = null
	int prevLevel = 0
	int thisLevel = 0
	int count = 0
	int l = 0
	int maxLevel = 0
	Skip objects = create
	
	
	for o in m do
	{
		put(objects, count++, o)
	}
	
	for o in objects do
	{
		thisLevel = o.ATTR_NEW_LEVEL
		
		if (thisLevel > maxLevel)
		{
			maxLevel = thisLevel
		}
	}
	
	
	for (l = maxLevel; l > 1; l--)
	{
		for o in objects do
		{
			thisLevel = o.ATTR_NEW_LEVEL
			
			if (thisLevel == l - 1)
			{
				oParent = o
			}
			else if (thisLevel == l)
			{
				move(o, last below oParent)
			}
		}
	}
	
	
	delete(objects)
}


/******************************************************************************
	MAIN
******************************************************************************/
Module currModule = current Module
string res = ""

if (!isEdit(currModule))
{
	infoBox("This utility can only be run in exclusive edit mode")
	halt
}

// check that the required level indicator attribute exists
createLevelAttr(currModule)

// ensure flat module hierarchy before starting - i.e. all objects at level 1
res = flattenHierarchy(currModule)

if (res != "")
{
	infoBox(res)
	halt
}




setInitialLevel(currModule)

refineLevels(currModule)

moveObjects(currModule)




sitemap