Rebuild Object Hierarchy after Import

This script 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.

//  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)