Merge branch 'master' of https://bitbucket.org/appfusions/universal-wiki-converter
diff --git a/.gitignore b/.gitignore
index 2443c87..473372b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@
 sampleData/*/*Output*
 output
 tmp/
+archive
diff --git a/conf/converter.dokuwiki.properties b/conf/converter.dokuwiki.properties
index 3d33c8e..d8d1f8e 100644
--- a/conf/converter.dokuwiki.properties
+++ b/conf/converter.dokuwiki.properties
@@ -75,7 +75,7 @@
 # Code (needs to be early so we can tokenize the contents)

 DokuWiki.002.code.java-regex-tokenizer=(?s)<code>(.*?)<\/code>{replace-with}{code}$1{code}

 DokuWiki.002.code-tsql.java-regex-tokenizer=(?s)\<code (tsql)\>(.*?)<\/code>{replace-with}{code:sql}$2{code}

-DokuWiki.002.code-type.java-regex-tokenizer=(?s)\<code ([^>]+?)\>(.*?)<\/code>{replace-with}{code:$1}$2{code}

+DokuWiki.002.code-type.java-regex-tokenizer=(?s)\<code ([^> ]+).*?\>(.*?)<\/code>{replace-with}{code:$1}$2{code}

 DokuWiki.002.noformat.java-regex-tokenizer=(?s)%%(.*?)%%{replace-with}{noformat}$1{noformat}

 # We do this after code because code blocks often have leading spaces and we

 # don't want to transform a code block more than once

@@ -136,6 +136,7 @@
 

 # Tables -- must be run before the image and link converters

 DokuWiki.21.prep-colspans.class=com.atlassian.uwc.converters.dokuwiki.PrepColSpansConverter

+DokuWiki.22.prep-rowspans.class=com.atlassian.uwc.converters.dokuwiki.PrepRowSpansConverter

 DokuWiki.23.table1.perl=s/\^/||/g

 

 # Interwiki links

diff --git a/readme.txt b/readme.txt
index e0dbfee..ea8efa0 100644
--- a/readme.txt
+++ b/readme.txt
@@ -10,6 +10,3 @@
 * chmod a+x *sh

 * ./run_uwc_devel.sh

 

-For more info, please see:

-

-https://studio.plugins.atlassian.com/wiki/display/UWC/Universal+Wiki+Converter

diff --git a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiAttachmentConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiAttachmentConverter.java
index e10b9c2..30f2825 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiAttachmentConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiAttachmentConverter.java
@@ -38,6 +38,9 @@
 				String dokupath = partsFinder.group(1);
 				String fileRelPath = createRelativePath(dokupath, relpath);
 				File file = new File(attdir + File.separator + fileRelPath);
+				if (!file.exists()) {
+					file = altCase(file);
+				}
 				candidates.add(file);
 				replacement = file.getName();
 				Matcher typeFinder = type.matcher(file.getName());
@@ -68,6 +71,25 @@
 		return input;
 	}
 
+	protected File altCase(File file) {
+		String name = file.getName();
+		File parent = file.getParentFile();
+		if (parent == null || !parent.exists()) {
+			log.warn("File probably does not exist: " + file.getAbsolutePath());
+			return file; 
+		}
+		File[] files = parent.listFiles();
+		if (files == null) {
+			log.warn("File probably does not exist: " + file.getAbsolutePath());
+			return file;
+		}
+		for (File realFile : files) {
+			if (name.equalsIgnoreCase(realFile.getName())) return realFile; 
+		}
+		log.warn("File probably does not exist: " + file.getAbsolutePath());
+		return file;
+	}
+
 	private String getSizeData(Matcher partsFinder) {
 		String hasOtherData = partsFinder.group(2);
 		String otherData = partsFinder.group(3);
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiAttachmentConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiAttachmentConverterTest.java
index 13175a8..5874971 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiAttachmentConverterTest.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiAttachmentConverterTest.java
@@ -146,4 +146,13 @@
 		assertNotNull(actual);
 		assertEquals(expected, actual);
 	}
+	
+	public void testAltCase() {
+		File file = new File("sampleData/dokuwiki/sampledokuwiki-inputbasic.txt");
+		File actual = tester.altCase(file);
+		String expected = "SampleDokuwiki-InputBasic.txt";
+		assertNotNull(actual);
+		assertEquals(expected, actual.getName());
+		assertTrue(actual.exists());
+	}
 }
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
index 9544b6f..8c982f7 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
@@ -32,8 +32,7 @@
 		try {
 			changeContent = FileUtils.readTextFile(changeFile);
 		} catch (IOException e) {
-			log.error("Could not read changes file: " + changeFilepath +". Skipping.");
-			e.printStackTrace();
+			log.error("Could not read changes file: " + changeFilepath +". Skipping.", e);
 			return;
 		}
 		//not preserving history at this time
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
index 51006c8..e84919b 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
@@ -55,8 +55,10 @@
 			if (target.startsWith("\\\\")) continue; //UNC link
 			if (target.contains("|")) {
 				String[] parts = target.split("\\|");
-				target = parts[0];
-				alias = parts[1];
+				if (parts.length > 1) {
+					target = parts[0];
+					alias = parts[1];
+				}
 			}
 			//FIXME anchors? we're not ready to transform these, so just get rid of the anchor part
 			if (target.contains("#")) target = target.replaceAll("#[^|]*", "");
@@ -84,8 +86,10 @@
 				//figure out if we've already got the space represented
 				String targetPart1 = target.replaceFirst(":[^:]+$", "");
 				boolean containsSpace = false;
-				if (namespaces.containsKey(target.replaceAll(":", "/"))) {
-					targetPart1 = target;
+//				log.debug("targetPart1 = " + targetPart1);
+				String nsFromTarget = findNSFromTarget(namespaces, target);
+				if (nsFromTarget != null) {
+					targetPart1 = nsFromTarget;
 				}
 				if (allspaces.contains(targetPart1)) 
 					containsSpace = true;
@@ -97,8 +101,6 @@
 				boolean isOne = (onecolon.matcher(target)).matches();
 				if (!(containsSpace && isOne)) 
 					target = target.replaceFirst(":start$", "");
-				if (containsSpace) //remove the space from the target for now
-					target = target.replaceFirst("\\Q"+targetPart1+"\\E:", "");
 				String hierarchy = target; //save for later
 				//is there a meta title to be used?
 //				log.debug("pagepath = " + pagepath);
@@ -149,6 +151,16 @@
 		}
 		return input;
 	}
+
+	protected String findNSFromTarget(HashMap<String, String> namespaces, String target) {
+		target = target.replaceAll(":", "/");
+		while (!"".equals(target)) {
+			if (namespaces.containsKey(target)) { return target; }
+			if (target.contains("/")) target = target.replaceAll("[/][^/]*$", "");
+			else return null;
+		}
+		return null;
+	}
 	
 	
 	private void blogLinkReport(HashMap<String, String> namespaces,
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
index b687921..ba485d4 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
@@ -131,15 +131,24 @@
 
 	public void testConvertWithPageByPageSpaces_2() {
 		tester.getProperties().setProperty("space-lala","ns/tada");
+		tester.getProperties().setProperty("space-foo","ns/tada/other");
 		Page page = new Page(new File(HierarchyTitleConverterTest.PAGESDIR+"/SampleDokuwiki-InputTitle.txt"));
 		page.setOriginalText("[[.:home]]\n" +
-				"[[ns:tada]]\n");
+				"[[ns:tada]]\n" +
+				"[[ns:tada:subchild]]\n" +
+				"[[ns:tada:other:subchild]]\n" +
+				"[[ns:tada:Other:subchild]]\n" +
+				"[[ns:tada:other:sub:subsubchild]]\n");
 		String spacekey = "otherspace";
 		page.setSpacekey(spacekey);//default spacekey is 'food'
 		tester.convert(page);
 		String actual = page.getConvertedText();
 		String expected = "[" + spacekey + ":Home]\n" + //this one users the current home
-				"[lala:Tada]\n"; //this one uses the mapping (drink points to food)
+				"[lala:Tada]\n" +
+				"[lala:Subchild]\n" +
+				"[foo:Subchild]\n" +
+				"[foo:Subchild]\n" +
+				"[foo:Subsubchild]\n"; //this one uses the mapping (drink points to food)
 		assertNotNull(actual);
 		assertEquals(expected, actual);
 	}
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTarget.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTarget.java
index dc911c6..74a3455 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTarget.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTarget.java
@@ -141,7 +141,7 @@
 		return full.replaceAll("\\Q"+ignorable + "\\E", "");
 	}
 
-	protected String getMetaFilename(String path, String filetype) {
+	public String getMetaFilename(String path, String filetype) {
 		String ignorable = getProperties().getProperty("filepath-hierarchy-ignorable-ancestors", null);
 		if (ignorable == null) {
 			return null;
@@ -166,5 +166,5 @@
 		log.debug("HierarchyTarget: relative path = " + path);
 		return path;
 	}
-	
+		
 }
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverter.java
index f0f339b..79a22a1 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverter.java
@@ -18,7 +18,7 @@
 		page.setConvertedText(converted);
 	}
 
-	Pattern colspan = Pattern.compile("[|]{2,}");
+	Pattern colspan = Pattern.compile("[|^]{2,}");
 	protected String prep(String input) {
 		input = removeEmptyColspanLines(input);
 		Matcher spanFinder = colspan.matcher(input);
@@ -28,7 +28,7 @@
 			found = true;
 			String spans = spanFinder.group();
 			int spanlength = spans.length();
-			String replacement = DELIM +TOKENKEY+spanlength+DELIM +"|";
+			String replacement = DELIM +TOKENKEY+spanlength+DELIM +spans.substring(0, 1);
 			replacement = RegexUtil.handleEscapesInReplacement(replacement);
 			spanFinder.appendReplacement(sb, replacement);
 		}
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverterTest.java
index e8af0bc..866defe 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverterTest.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverterTest.java
@@ -17,10 +17,10 @@
 	public void testPrep() {
 		String input, expected, actual;
 		input = "^ Header ^ Header ^ Header ^ Header ^ Header ^ Header ^\n" +
-				"^ Header | 2 | 3 | 4 | 5 | 6 |^\n" +
+				"^ Header | 2 | 3 | 4 | 5 | 6 |\n" +
 				"^ Header | next colspans |||||\n";
 		expected ="^ Header ^ Header ^ Header ^ Header ^ Header ^ Header ^\n" +
-				"^ Header | 2 | 3 | 4 | 5 | 6 |^\n" + 
+				"^ Header | 2 | 3 | 4 | 5 | 6 |\n" + 
 				"^ Header | next colspans ::UWCTOKENCOLSPANS:5::|\n";
 		actual = tester.prep(input);
 		assertNotNull(actual);
@@ -100,4 +100,17 @@
 		assertNotNull(actual);
 		assertEquals(expected, actual);
 	}
+	
+	public void testPrepHeader() {
+		String input, expected, actual;
+		input = "^ h1             ^^^^^^ \n" +
+				"^ a1 ^  a2 ^  a3 ^  a4 ^  a5 ^ a6 ^\n" + 
+				"| r1 | r2 | r3 | r4  |r5 |r6 |\n";
+		expected ="^ h1             ::UWCTOKENCOLSPANS:6::^ \n" +
+				"^ a1 ^  a2 ^  a3 ^  a4 ^  a5 ^ a6 ^\n" + 
+				"| r1 | r2 | r3 | r4  |r5 |r6 |\n";
+		actual = tester.prep(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
 }
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/PrepRowSpansConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/PrepRowSpansConverter.java
new file mode 100644
index 0000000..783bf99
--- /dev/null
+++ b/src/com/atlassian/uwc/converters/dokuwiki/PrepRowSpansConverter.java
@@ -0,0 +1,128 @@
+package com.atlassian.uwc.converters.dokuwiki;
+
+import java.util.HashMap;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.atlassian.uwc.converters.BaseConverter;
+import com.atlassian.uwc.converters.tikiwiki.RegexUtil;
+import com.atlassian.uwc.ui.Page;
+
+public class PrepRowSpansConverter extends BaseConverter {
+
+
+	public static final String TOKENKEY = "UWCTOKENROWSPANS:";
+	private static final String DELIM = "::";
+	@Override
+	public void convert(Page page) {
+		String input = page.getOriginalText();
+		String converted = prep(input);
+		page.setConvertedText(converted);
+	}
+	
+	Pattern rowspan = Pattern.compile("(?<=^|\n)[|^](.*?):::(.*?)((\n(?=\\s*[^|^]))|$)", Pattern.DOTALL);
+	protected String prep(String input) {
+		Matcher rowspanFinder = rowspan.matcher(input);
+		StringBuffer sb = new StringBuffer();
+		boolean found = false;
+		while (rowspanFinder.find()) {
+			found = true;
+			String table = rowspanFinder.group();
+			//remove everything before the last table
+			String pre = "";
+			if (needToTrim(table)) {
+				String parts[] = trimTable(table);
+				if (parts.length > 1) {
+					pre = parts[0];
+					table = parts[1];
+				}
+			}
+			//how many columns
+			int numcols = getNumCols(table);
+			//Look for a cell without the signal. Does the one following have it?
+			for (int i = 0; i < numcols; i++) {
+				//for each :::, if the one above is not :::, put the token?
+				table = handleColumn(i, table);
+			}
+			table = RegexUtil.handleEscapesInReplacement(pre + table);
+			rowspanFinder.appendReplacement(sb, table);
+		}
+		if (found) {
+			rowspanFinder.appendTail(sb);
+			return sb.toString();
+		}
+		return input;
+	}
+	
+	Pattern moreThanOneTable = Pattern.compile("[|^]\n([^|^]|\n).*?[|^]", Pattern.DOTALL);
+	protected boolean needToTrim(String input) {
+		Matcher finder = moreThanOneTable.matcher(input);
+		return finder.find();
+	}
+
+	Pattern lastTable = Pattern.compile("^(.*?(?:(?:\n|[^|^])\n))([|^].*)", Pattern.DOTALL);
+	protected String[] trimTable(String input) {
+		Matcher lastFinder = lastTable.matcher(input);
+		if (lastFinder.find()) {
+			return new String[] {lastFinder.group(1),lastFinder.group(2)};
+		}
+		return new String[] {input};
+	}
+
+	String cellstring = "[|^][^|^]*";
+	Pattern cell = Pattern.compile(cellstring);
+	protected String handleColumn(int i, String input) {
+		String pre = "(?<=^|\n)";
+		while (i-- > 0) { pre += cellstring;} 
+		Pattern p = Pattern.compile(pre+"("+cellstring+")");
+		Matcher colFinder = p.matcher(input);
+		int lastrow = -1;
+		int length = 0;
+		TreeMap<Integer, String> additions = new TreeMap<Integer, String>();
+		while (colFinder.find()) {
+			String cellcontents = colFinder.group(1);
+			if (!cellcontents.contains(":::")) {
+				if (length > 0) {
+					additions.put(lastrow, DELIM + TOKENKEY + (length+1) + DELIM);
+					length = 0;
+				}
+				lastrow = colFinder.end();
+			}
+			else {
+				length++;
+			}
+		}
+		if (length > 0) {
+			additions.put(lastrow, DELIM + TOKENKEY + (length+1) + DELIM);
+		}
+		Set<Integer> keySet = additions.keySet();
+		Vector<Integer> keys = new Vector<Integer>(keySet);
+		if (keys.isEmpty()) return input;
+		String output = "";
+		int first = 0, last = 0;
+		for (int index = 0; index < keys.size(); index ++ ) {
+			last = keys.get(index);
+			String part = input.substring(first, last);
+			output += part + additions.get(last); 
+			first = last;
+		}
+		output += input.substring(last);
+		return output;
+	}
+
+	Pattern line = Pattern.compile("^[^\n]+", Pattern.MULTILINE);
+	protected int getNumCols(String input) {
+		Matcher lineFinder = line.matcher(input);
+		while (lineFinder.find()) {
+			String firstline = lineFinder.group();
+			if (firstline.contains(PrepColSpansConverter.TOKENKEY)) continue;
+			String[] cols = firstline.split("[|^]");
+			return cols.length - 1;
+		}
+		return 0;
+	}
+
+}
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/PrepRowSpansConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/PrepRowSpansConverterTest.java
new file mode 100644
index 0000000..ead11eb
--- /dev/null
+++ b/src/com/atlassian/uwc/converters/dokuwiki/PrepRowSpansConverterTest.java
@@ -0,0 +1,265 @@
+package com.atlassian.uwc.converters.dokuwiki;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+
+public class PrepRowSpansConverterTest extends TestCase {
+
+	PrepRowSpansConverter tester = null;
+	Logger log = Logger.getLogger(this.getClass());
+	protected void setUp() throws Exception {
+		tester = new PrepRowSpansConverter();
+		PropertyConfigurator.configure("log4j.properties");
+	}
+
+	public void testPrep_MultiRow() {
+		String input, expected, actual;
+		input = "^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a | b     | a         | b             |\n" + 
+				"| :::                | b      | :::                | d              |\n" + 
+				"| :::                | b  | :::                | d |\n" + 
+				"| :::                | b | :::                | d |\n" + 
+				"^ 1 ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a        | b               | c     | d |\n" + 
+				"| :::                |b             | :::                | d        |\n" + 
+				"| :::                | b    | :::                | d     |\n" + 
+				"| :::                |b     | :::                | d     |\n" + 
+				"";
+		expected ="^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a ::UWCTOKENROWSPANS:4::| b     | a         ::UWCTOKENROWSPANS:4::| b             |\n" + 
+				"| :::                | b      | :::                | d              |\n" + 
+				"| :::                | b  | :::                | d |\n" + 
+				"| :::                | b | :::                | d |\n" + 
+				"^ 1 ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a        ::UWCTOKENROWSPANS:4::| b               | c     ::UWCTOKENROWSPANS:4::| d |\n" + 
+				"| :::                |b             | :::                | d        |\n" + 
+				"| :::                | b    | :::                | d     |\n" + 
+				"| :::                |b     | :::                | d     |\n" + 
+				"";
+		actual = tester.prep(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testPrep_MultiRow_b() {
+		String input, expected, actual;
+		input = "^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a | b     | a         | b             |\n" + 
+				"| :::                | b      | :::                | d              |\n" + 
+				"| :::                | b  | :::                | d |\n" + 
+				"| :::                | b | :::                | d |\n" + 
+				"^ 1 ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a        | b               | c     | d |\n" + 
+				"| :::                |b             | :::                | d        |\n" + 
+				"| :::                | b    | :::                | d     |\n" + 
+				"| :::                |b     | c                | d     |\n" + 
+				"no longer a table";
+		expected ="^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a ::UWCTOKENROWSPANS:4::| b     | a         ::UWCTOKENROWSPANS:4::| b             |\n" + 
+				"| :::                | b      | :::                | d              |\n" + 
+				"| :::                | b  | :::                | d |\n" + 
+				"| :::                | b | :::                | d |\n" + 
+				"^ 1 ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a        ::UWCTOKENROWSPANS:4::| b               | c     ::UWCTOKENROWSPANS:3::| d |\n" + 
+				"| :::                |b             | :::                | d        |\n" + 
+				"| :::                | b    | :::                | d     |\n" + 
+				"| :::                |b     | c                | d     |\n" + 
+				"no longer a table";
+		actual = tester.prep(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testGetNumCols() {
+		String input;
+		int expected, actual;
+		input = "^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a | b     | a         | b             |\n" + 
+				"| :::                | b      | :::                | d              |\n";
+		expected = 4;
+		actual = tester.getNumCols(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	
+	public void testGetNumCols_WithColspan() {
+		String input;
+		int expected, actual;
+		input = "^ h1  ::UWCTOKENCOLSPANS:6::^\n" + 
+				"^ h1       ^ h2 ^ h3 ^ h4 ^ h5 ^ h6 ^\n" + 
+				"| Trala        | abc.def.ghu.jkl.edu       | D:\\Path\\TO\\Some\\Place_tada\\asd     | D:\\Path\\TO\\Some\\Place_tada\\asd[mmyyyy].txt  | asd.asd.asd.org  | Asdlkj_askjda_BD  |\n" + 
+				"| :::           | foo-1234-bar.abc.def.org:0986  | :::                                       | D:\\AppLogFiles\\BindingLogs_[yyyy-mm].txt        | :::                                      | :::             |\n" + 
+				"| Lala         | a.b.c.d.e       | D:\\Apps\\INT\\Do\\ComparioXMLSite_es\\Web     | D:\\AppLogFiles\\ComparioXmlSite.xml[mmyyyy].txt  | sqlcompariopreview.internet.conseur.org  | Compario_ES_ES  |\n" + 
+				"";
+		expected = 6;
+		actual = tester.getNumCols(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testHandleColumn_0() {
+		String input, expected, actual;
+		input = "^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a | b     | a         | b             |\n" + 
+				"| :::                | b      | :::                | d              |\n" + 
+				"| :::                | b  | :::                | d |\n" + 
+				"| :::                | b | :::                | d |\n" + 
+				"^ 1 ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a        | b               | c     | d |\n" + 
+				"| :::                |b             | :::                | d        |\n";
+		expected = "^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a ::UWCTOKENROWSPANS:4::| b     | a         | b             |\n" + 
+				"| :::                | b      | :::                | d              |\n" + 
+				"| :::                | b  | :::                | d |\n" + 
+				"| :::                | b | :::                | d |\n" + 
+				"^ 1 ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a        ::UWCTOKENROWSPANS:2::| b               | c     | d |\n" + 
+				"| :::                |b             | :::                | d        |\n";
+		actual = tester.handleColumn(0, input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testHandleColumn_2() {
+		String input, expected, actual;
+		input = "^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a | b     | a         | b             |\n" + 
+				"| :::                | b      | :::                | d              |\n" + 
+				"| :::                | b  | :::                | d |\n" + 
+				"| :::                | b | :::                | d |\n" + 
+				"^ 1 ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a        | b               | c     | d |\n" + 
+				"| :::                |b             | :::                | d        |\n";
+		expected = "^ 1            ^ 2                ^ 1            ^ 2                ^\n" + 
+				"| a | b     | a         ::UWCTOKENROWSPANS:4::| b             |\n" + 
+				"| :::                | b      | :::                | d              |\n" + 
+				"| :::                | b  | :::                | d |\n" + 
+				"| :::                | b | :::                | d |\n" + 
+				"^ 1 ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a        | b               | c     ::UWCTOKENROWSPANS:2::| d |\n" + 
+				"| :::                |b             | :::                | d        |\n";
+		actual = tester.handleColumn(2, input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testExample() {
+		String input, expected, actual;
+		input = "^ h1  ^ h2                           ^ h3                                       ^ h4                                       ^ h5                                       ^ h6                                                        ^\n" + 
+				"^ r1c1   | r1c2  |  r1c3| r1c4            | r1c5 | r1c6 |\n" + 
+				"^ :::          | r2c2                       | r2c3| c4  | c5 | c6 |\n" + 
+				"^ :::          | c2 | c3|c4| c5|c6  |\n" + 
+				"^ :::          | c2 | c3|c4| c5|c6  |\n" + 
+				"^ Health page  | c2  ::UWCTOKENCOLSPANS:5::|\n" + 
+				"";
+		expected = "^ h1  ^ h2                           ^ h3                                       ^ h4                                       ^ h5                                       ^ h6                                                        ^\n" + 
+				"^ r1c1   ::UWCTOKENROWSPANS:4::| r1c2  |  r1c3| r1c4            | r1c5 | r1c6 |\n" + 
+				"^ :::          | r2c2                       | r2c3| c4  | c5 | c6 |\n" + 
+				"^ :::          | c2 | c3|c4| c5|c6  |\n" + 
+				"^ :::          | c2 | c3|c4| c5|c6  |\n" + 
+				"^ Health page  | c2  ::UWCTOKENCOLSPANS:5::|\n";
+		actual = tester.prep(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testExample2() {
+		String input, expected, actual;
+		input = "^ h1  ::UWCTOKENCOLSPANS:6::^\n" + 
+				"^ h1       ^ h2 ^ h3 ^ h4 ^ h5 ^ h6 ^\n" + 
+				"| Trala        | abc.def.ghu.jkl.edu       | D:\\Path\\TO\\Some\\Place_tada\\asd     | D:\\Path\\TO\\Some\\Place_tada\\asd[mmyyyy].txt  | asd.asd.asd.org  | Asdlkj_askjda_BD  |\n" + 
+				"| :::           | foo-1234-bar.abc.def.org:0986  | :::                                       | D:\\AppLogFiles\\BindingLogs_[yyyy-mm].txt        | :::                                      | :::             |\n" + 
+				"| Lala         | a.b.c.d.e       | D:\\Apps\\INT\\Do\\ComparioXMLSite_es\\Web     | D:\\AppLogFiles\\ComparioXmlSite.xml[mmyyyy].txt  | sqlcompariopreview.internet.conseur.org  | Compario_ES_ES  |\n" + 
+				"";
+		expected = "^ h1  ::UWCTOKENCOLSPANS:6::^\n" + 
+				"^ h1       ^ h2 ^ h3 ^ h4 ^ h5 ^ h6 ^\n" + 
+				"| Trala        ::UWCTOKENROWSPANS:2::| abc.def.ghu.jkl.edu       | D:\\Path\\TO\\Some\\Place_tada\\asd     ::UWCTOKENROWSPANS:2::| D:\\Path\\TO\\Some\\Place_tada\\asd[mmyyyy].txt  | asd.asd.asd.org  ::UWCTOKENROWSPANS:2::| Asdlkj_askjda_BD  ::UWCTOKENROWSPANS:2::|\n" + 
+				"| :::           | foo-1234-bar.abc.def.org:0986  | :::                                       | D:\\AppLogFiles\\BindingLogs_[yyyy-mm].txt        | :::                                      | :::             |\n" + 
+				"| Lala         | a.b.c.d.e       | D:\\Apps\\INT\\Do\\ComparioXMLSite_es\\Web     | D:\\AppLogFiles\\ComparioXmlSite.xml[mmyyyy].txt  | sqlcompariopreview.internet.conseur.org  | Compario_ES_ES  |\n" + 
+				"";
+		actual = tester.prep(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testExample3() {
+		String input, expected, actual;
+		input = "Given the following:\n" + 
+				"\n" + 
+				"^ 1    | 2               |\n" + 
+				"^ 1   | 2 |\n" + 
+				"^ 1 | 2                     |\n" + 
+				"\n" + 
+				"tralala:\n" + 
+				"\n" + 
+				"^ 1           ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a | b | c | !!! |\n" + 
+				"| a | b | c |::: |\n" + 
+				"| a | b | c | end |\n" + 
+				"";
+		expected = "Given the following:\n" + 
+				"\n" + 
+				"^ 1    | 2               |\n" + 
+				"^ 1   | 2 |\n" + 
+				"^ 1 | 2                     |\n" + 
+				"\n" + 
+				"tralala:\n" + 
+				"\n" + 
+				"^ 1           ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a | b | c | !!! ::UWCTOKENROWSPANS:2::|\n" + 
+				"| a | b | c |::: |\n" + 
+				"| a | b | c | end |\n" + 
+				"";
+		actual = tester.prep(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testTrimTable() {
+		String input, expected, actual;
+		input = "^ 1    | 2               |\n" + 
+				"^ 1   | 2 |\n" + 
+				"^ 1 | 2                     |\n" + 
+				"\n" + 
+				"tralala:\n" + 
+				"\n" + 
+				"^ 1           ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a | b | c | !!! ::UWCTOKENROWSPANS:2::|\n" + 
+				"| a | b | c |::: |\n" + 
+				"| a | b | c | end |\n";
+		expected = "^ 1           ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a | b | c | !!! ::UWCTOKENROWSPANS:2::|\n" + 
+				"| a | b | c |::: |\n" + 
+				"| a | b | c | end |\n";
+		actual = tester.trimTable(input)[1];
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testNeedToTrim() {
+		String input, expected, actual;
+		input = "^ 1    | 2               |\n" + 
+				"^ 1   | 2 |\n" + 
+				"^ 1 | 2                     |\n" + 
+				"\n" + 
+				"tralala:\n" + 
+				"\n" + 
+				"^ 1           ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a | b | c | !!!|\n" + 
+				"| a | b | c |::: |\n" + 
+				"| a | b | c | end |\n";
+		
+		assertTrue(tester.needToTrim(input));
+		
+		input = "tralala:\n" + 
+				"\n" + 
+				"^ 1           ^ 2 ^ 3 ^ 4 ^\n" + 
+				"| a | b | c | !!!|\n" + 
+				"| a | b | c |::: |\n" + 
+				"| a | b | c | end |\n";
+		assertFalse(tester.needToTrim(input));
+	}
+}
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/SpaceConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/SpaceConverter.java
index 1d22fed..f3b306d 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/SpaceConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/SpaceConverter.java
@@ -43,7 +43,7 @@
 			//remove deepest portion of the path
 			tmppath = removeDeepest(tmppath);
 		}
-		
+		log.debug("spacekey set to: " + page.getSpacekey());
 	}
 	
 	static Pattern filetype = Pattern.compile("[.]\\w+$");
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/TableRowColSpanConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/TableRowColSpanConverter.java
index a99dceb..76ec28f 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/TableRowColSpanConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/TableRowColSpanConverter.java
@@ -19,10 +19,10 @@
 		String input = page.getOriginalText();
 		String tmpconverted = convertColspans(input);
 		if (!(page instanceof VersionPage) && !input.equals(tmpconverted)) 
-			log.debug("Colspans detected: " + page.getName());
+			log.debug("Colspans detected: '" + page.getName() + "' in space: " + page.getSpacekey());
 		String converted = convertRowspans(tmpconverted);
 		if (!(page instanceof VersionPage) && !tmpconverted.equals(converted)) 
-			log.debug("Rowspans detected: " + page.getName());
+			log.debug("Rowspans detected: '" + page.getName() + "' in space: " + page.getSpacekey());
 		page.setConvertedText(converted);
 	}
 
@@ -61,185 +61,48 @@
 		}
 		return input;
 	}
-
-	Pattern table = Pattern.compile("<table>(.*?)</table>", Pattern.DOTALL);
+	
+	Pattern uwctokenrowspan = Pattern.compile("::" + PrepRowSpansConverter.TOKENKEY + "(\\d+)::");
 	Pattern rowspan = Pattern.compile(":::");
-	Pattern tablerow = Pattern.compile("<tr>(.*?)</tr>", Pattern.DOTALL);
-	Pattern tdWithColspan = Pattern.compile("<t([dh])(?: colspan='(\\d+)')?>(.*?)</t[dh]>", Pattern.DOTALL);
 	protected String convertRowspans(String input) {
-		Matcher tableFinder = table.matcher(input);
+		Matcher tdFinder = td.matcher(input);
 		StringBuffer sb = new StringBuffer();
 		boolean found = false;
-		while (tableFinder.find()) {
-			Vector<Integer> rowindeces = new Vector<Integer>();
-			Vector<Integer> colindeces = new Vector<Integer>();
-			Vector<Integer> rowvals = new Vector<Integer>();
-			Vector<Integer> colclear = new Vector<Integer>();
+		while (tdFinder.find()) {
 			found = true;
-			String tableContents = tableFinder.group(1);
-			Matcher rowspanFinder = rowspan.matcher(tableContents);
-			if (!rowspanFinder.find()) continue;
-
-			int rowspanVal = 1; //the value of the rowspan attribute
-			int lastrow = 0;
-			int rowspancount = 0;
-
-			Matcher rowFinder = tablerow.matcher(tableContents);
-			StringBuffer rowsb = new StringBuffer();
-			boolean rowfound = false;
-			boolean noteindex = true;
-
-			boolean clearrow = false;
-			while (rowFinder.find()) {
-				int lastcol = -1;
-				rowfound = true;
-				boolean newrow = true;
-
-				String rowcontents = rowFinder.group(1);
-				Matcher tdFinder = tdWithColspan.matcher(rowcontents);
-				StringBuffer tdsb = new StringBuffer();
-				boolean tdfound = false;
-				boolean rowspanfoundLast = true;
-				boolean rowspanfoundCurrent = true;
-				int rowspancountThisRow = 0;
-				int currentColOffset = 0;
-				while (tdFinder.find()) {
-					tdfound = true;
-					lastcol++;
-					String cell = tdFinder.group(3);
-					String colspanOffset = tdFinder.group(2);
-
-					rowspanFinder = rowspan.matcher(cell);
-					rowspanfoundLast = rowspanfoundCurrent;
-					rowspanfoundCurrent = rowspanFinder.find();
-					if (!rowspanfoundCurrent) { //no rowspan
-						colclear.add(lastcol+currentColOffset);
-					}
-					else { //found a rowspan!
-						tdFinder.appendReplacement(tdsb, ""); //remove the ::: cells
-						if (newrow && clearrow && rowspanVal > 1) {
-							rowspancount++;
-							fillRowSpanVals(rowindeces, rowvals, rowspanVal);
-							rowspanVal = currentColOffset+1;
-							colindeces.add(lastcol); //note the index of the current cell
-							rowindeces.add(lastrow-1);//note the index of the previous row
-							noteindex = false;
-						}
-						rowspanVal++;
-
-						if (noteindex || !newrow) {
-							rowspancount++;
-							rowspancountThisRow++;
-							colindeces.add(lastcol); //note the index of the current cell
-							rowindeces.add(lastrow-1);//note the index of the previous row
-							noteindex = false;
-							if (!newrow && rowspancountThisRow > 0) rowspanVal--;
-							colclear.removeAllElements();
-						}
-						else if (!rowspanfoundLast) {
-							noteindex = true; 
-							rowvals.add(rowspanVal);
-							rowspanVal=currentColOffset+1;
-						}
-
-						newrow = false;
-					}
-					if (colspanOffset != null) 
-						currentColOffset += (Integer.parseInt(colspanOffset));
-				}
-				if (tdfound) {
-					tdFinder.appendTail(tdsb);
-					rowcontents = "<tr>"+tdsb.toString()+"</tr>";
-				}
-				else
-					rowcontents = "<tr>" + rowcontents + "</tr>";
-
-
-				String replacement = rowcontents;
-				replacement = RegexUtil.handleEscapesInReplacement(replacement);
-				rowFinder.appendReplacement(rowsb, replacement);
-				lastrow++;
-				boolean tmpclear = true;
-				for (int i = 0; i < lastcol-1; i++) {
-					if (!colclear.contains(i+currentColOffset)) tmpclear = false; 
-				}
-				clearrow = tmpclear;
-				colclear.removeAllElements();
+			String type = tdFinder.group(1);
+			String row = tdFinder.group(2);
+			Matcher rowspanFinder = rowspan.matcher(row);
+			if (rowspanFinder.find()) {
+				tdFinder.appendReplacement(sb, "");
+				continue;
 			}
-			if (rowfound) {
-				fillRowSpanVals(rowindeces, rowvals, rowspanVal);
-				rowspanVal=1;
-				rowFinder.appendTail(rowsb);
-				tableContents = rowsb.toString();
+			
+			Matcher uwctokenFinder = uwctokenrowspan.matcher(row);
+			boolean found2 = false;
+			String len = "";
+			StringBuffer sb2 = new StringBuffer();
+			while (uwctokenFinder.find()) {
+				found2 = true;
+				len = uwctokenFinder.group(1);
+				String rep2= RegexUtil.handleEscapesInReplacement("");
+				uwctokenFinder.appendReplacement(sb2, rep2);
 			}
-
-			rowFinder = tablerow.matcher(tableContents);
-			rowsb = new StringBuffer();
-			rowfound = false;
-			int currentrow = 0;
-			int index = 0;
-			while (rowFinder.find()) {
-				rowfound = true;
-				String rowcontents = rowFinder.group(1);
-
-				if (index >= rowspancount) break;
-				int rowindex = rowindeces.get(index);
-				int colindex = colindeces.get(index);
-				int rowval = rowvals.get(index);
-				if (currentrow == rowindex) {
-					Matcher tdFinder = td.matcher(rowcontents);
-					StringBuffer tdsb = new StringBuffer();
-					boolean tdfound = false;
-
-					int currentcell = 0;
-					while (tdFinder.find()) {
-						tdfound = true;
-						String type = tdFinder.group(1);
-						if (currentcell == colindex) {
-							String newcell = "<t"+type+" rowspan='"+rowval+"'>"+tdFinder.group(2)+"</t"+type+">";
-							tdFinder.appendReplacement(tdsb, newcell); //replace the rowspan cell
-							index++;
-							if (index < rowspancount) { //get these now in case we have more rowspans in the same row
-								colindex = colindeces.get(index); 
-								rowval = rowvals.get(index);
-							}
-						}
-						currentcell++;
-					}
-					if (tdfound) {
-						tdFinder.appendTail(tdsb);
-						rowcontents = tdsb.toString();
-					}
-				}
-
-				String replacement = "<tr>"+rowcontents+"</tr>";
-				replacement = RegexUtil.handleEscapesInReplacement(replacement);
-				rowFinder.appendReplacement(rowsb, replacement);
-				currentrow++;
+			if (found2) {
+				uwctokenFinder.appendTail(sb2);
+				row = sb2.toString();
 			}
-			if (rowfound) {
-				rowFinder.appendTail(rowsb);
-				tableContents = rowsb.toString();
-			}
-
-			String replacement = "<table>" + tableContents + "</table>";
+			else continue;
+			String replacement = "<t"+type+" rowspan='"+len+"'>"+row+"</t"+type+">";
 			replacement = RegexUtil.handleEscapesInReplacement(replacement);
-			tableFinder.appendReplacement(sb, replacement);
+			tdFinder.appendReplacement(sb, replacement);
 		}
 		if (found) {
-			tableFinder.appendTail(sb);
+			tdFinder.appendTail(sb);
 			return sb.toString();
 		}
 		return input;
 	}
-	public void fillRowSpanVals(Vector<Integer> indeces, Vector<Integer> vals, int val) {
-		vals.add(val);
-		int last = indeces.get(vals.size()-1);
-		while (indeces.size() > vals.size()) {
-			if (indeces.get(vals.size()-1) == last)
-				vals.add(val);
-			else break;
-		}
-	}
+
 
 }
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/TableRowColSpanConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/TableRowColSpanConverterTest.java
index e0e472a..b233331 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/TableRowColSpanConverterTest.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/TableRowColSpanConverterTest.java
@@ -130,7 +130,7 @@
 				"<th><p> Head 6 </p></th>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
-				"<th><p> Row 1 </p></th>\n" + 
+				"<th><p> Row 1 ::UWCTOKENROWSPANS:4::</p></th>\n" + 
 				"<td> Row 2 </td>\n" + 
 				"<td> Row 3 </td>\n" +
 				"<td> Row 4 </td>\n" +
@@ -233,7 +233,7 @@
 				"</tr>\n" + 
 				"<tr>\n" + 
 				"<td><p> Row 4 Col 1    </p></td>\n" + 
-				"<td><p> this cell spans vertically </p></td>\n" + 
+				"<td><p> this cell spans vertically ::UWCTOKENROWSPANS:3::</p></td>\n" + 
 				"<td><p> Row 4 Col 3        </p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
@@ -255,7 +255,7 @@
 				"<td><p> Row 8 some colspan (note the double pipe) ::UWCTOKENCOLSPANS:3::</p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
-				"<td><p> Row 9 Col 1    </p></td>\n" + 
+				"<td><p> Row 9 Col 1    ::UWCTOKENROWSPANS:3::</p></td>\n" + 
 				"<td><p> Row 9 Col 2     </p></td>\n" + 
 				"<td><p> Row 9 Col 3        </p></td>\n" + 
 				"</tr>\n" + 
@@ -360,7 +360,7 @@
 				"</tr>\n" + 
 				"<tr>\n" + 
 				"<td><p> Row 4 Col 1    </p></td>\n" + 
-				"<td><p> this cell spans vertically </p></td>\n" + 
+				"<td><p> this cell spans vertically ::UWCTOKENROWSPANS:3::</p></td>\n" + 
 				"<td><p> Row 4 Col 3        </p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
@@ -384,7 +384,7 @@
 				"<td><p> Row 8 Col 3</p></td>\n" +
 				"</tr>\n" + 
 				"<tr>\n" + 
-				"<td><p> Row 9 Col 1    </p></td>\n" + 
+				"<td><p> Row 9 Col 1    ::UWCTOKENROWSPANS:3::</p></td>\n" + 
 				"<td><p> Row 9 Col 2     </p></td>\n" + 
 				"<td><p> Row 9 Col 3        </p></td>\n" + 
 				"</tr>\n" + 
@@ -481,8 +481,8 @@
 				"</tr>\n" +
 				"<tr>\n" + 
 				"<td><p> Row 1 Col 1    </p></td>\n" + 
-				"<td><p> Row 1 Col 2     </p></td>\n" + 
-				"<td><p> Row 1 Col 3        </p></td>\n" + 
+				"<td><p> Row 1 Col 2     ::UWCTOKENROWSPANS:4::</p></td>\n" + 
+				"<td><p> Row 1 Col 3        ::UWCTOKENROWSPANS:3::</p></td>\n" + 
 				"</tr>\n" +
 				"<tr>\n" + 
 				"<td><p> Row 2 Col 1    </p></td>\n" + 
@@ -559,7 +559,7 @@
 				"</tr>\n" + 
 				"<tr>\n" + 
 				"<td><p> Row 4 Col 1    </p></td>\n" + 
-				"<td><p> this cell spans vertically </p></td>\n" + 
+				"<td><p> this cell spans vertically ::UWCTOKENROWSPANS:3::</p></td>\n" + 
 				"<td><p> Row 4 Col 3        </p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
@@ -581,7 +581,7 @@
 				"<td><p> Row 8 some colspan (note the double pipe) ::UWCTOKENCOLSPANS:3::</p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
-				"<td><p> Row 9 Col 1    </p></td>\n" + 
+				"<td><p> Row 9 Col 1    ::UWCTOKENROWSPANS:3::</p></td>\n" + 
 				"<td><p> Row 9 Col 2     </p></td>\n" + 
 				"<td><p> Row 9 Col 3        </p></td>\n" + 
 				"</tr>\n" + 
@@ -605,8 +605,8 @@
 				"</tr>\n" + 
 				"<tr>\n" + 
 				"<td><p> Row 1 Col 1    </p></td>\n" + 
-				"<td><p> Row 1 Col 2     </p></td>\n" + 
-				"<td><p> Row 1 Col 3        </p></td>\n" + 
+				"<td><p> Row 1 Col 2     ::UWCTOKENROWSPANS:4::</p></td>\n" + 
+				"<td><p> Row 1 Col 3        ::UWCTOKENROWSPANS:3::</p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
 				"<td><p> Row 2 Col 1    </p></td>\n" + 
@@ -727,9 +727,9 @@
 				"<th><p> h4                </p></th>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
-				"<td><p> foo           </p></td>\n" + 
+				"<td><p> foo           ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"<td><p> bar               </p></td>\n" + 
-				"<td><p> baz      </p></td>\n" + 
+				"<td><p> baz      ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"<td><p> tralala          </p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
@@ -783,12 +783,12 @@
 				"<th><p> h6 </p></th>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
-				"<td><p> thin        </p></td>\n" + 
+				"<td><p> thin        ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"<td><p> man </p></td>\n" + 
-				"<td><p> starring </p></td>\n" + 
+				"<td><p> starring ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"<td><p> myrna </p></td>\n" + 
-				"<td><p> loy  </p></td>\n" + 
-				"<td><p> tralalala  </p></td>\n" + 
+				"<td><p> loy  ::UWCTOKENROWSPANS:2::</p></td>\n" + 
+				"<td><p> tralalala  ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
 				"<td><p> :::           </p></td>\n" + 
@@ -799,12 +799,12 @@
 				"<td><p> :::             </p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
-				"<td><p> foo         </p></td>\n" + 
+				"<td><p> foo         ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"<td><p> bar     </p></td>\n" + 
-				"<td><p> arg</p></td>\n" + 
+				"<td><p> arg::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"<td><p> this </p></td>\n" + 
-				"<td><p> is </p></td>\n" + 
-				"<td><p> annoying </p></td>\n" + 
+				"<td><p> is ::UWCTOKENROWSPANS:2::</p></td>\n" + 
+				"<td><p> annoying ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
 				"<td><p> :::           </p></td>\n" + 
@@ -815,12 +815,12 @@
 				"<td><p> :::             </p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
-				"<td><p> 1      </p></td>\n" + 
+				"<td><p> 1      ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"<td><p> 2    </p></td>\n" + 
-				"<td><p> 3   </p></td>\n" + 
+				"<td><p> 3   ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"<td><p> 4  </p></td>\n" + 
-				"<td><p> 5 </p></td>\n" + 
-				"<td><p> 6  </p></td>\n" + 
+				"<td><p> 5 ::UWCTOKENROWSPANS:2::</p></td>\n" + 
+				"<td><p> 6  ::UWCTOKENROWSPANS:2::</p></td>\n" + 
 				"</tr>\n" + 
 				"<tr>\n" + 
 				"<td><p> :::           </p></td>\n" + 
@@ -897,5 +897,230 @@
 		assertNotNull(actual);
 		assertEquals(expected, actual);
 	}
+	
+	public void testConvertColSpansWithHeader() {
+		String input, expected, actual;
+		input = "<table><tbody>\n" + 
+				"<tr>\n" + 
+				"<th><p> Head 1 ::UWCTOKENCOLSPANS:6:: </p></th>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> Row 1 </p></th>\n" + 
+				"<td> Row 2 </td>\n" + 
+				"<td> Row 3 </td>\n" +
+				"<td> Row 4 </td>\n" +
+				"<td> Row 5 </td>\n" +
+				"<td> Row 6 </td>\n" +
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> :::          </p></th>\n" + 
+				"<td><p> Row 2 </p></td>\n" + 
+				"<td><p> Row 3 </p></td>\n" +
+				"<td><p> Row 4 </p></td>\n" +
+				"<td><p> Row 5 </p></td>\n" +
+				"<td><p> Row 6 </p></td>\n" +
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> :::          </p></th>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td> Item 6 </td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> :::          </p></th>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td> Last 6 </td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> Header </p></th>\n" + 
+				"<td><p> Colspan Here ::UWCTOKENCOLSPANS:5::</p></td>\n" + 
+				"</tr>\n" + 
+				"</tbody></table>\n" + 
+				"";
+		expected = "<table><tbody>\n" + 
+				"<tr>\n" + 
+				"<th colspan='6'><p> Head 1  </p></th>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> Row 1 </p></th>\n" + 
+				"<td> Row 2 </td>\n" + 
+				"<td> Row 3 </td>\n" +
+				"<td> Row 4 </td>\n" +
+				"<td> Row 5 </td>\n" +
+				"<td> Row 6 </td>\n" +
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> :::          </p></th>\n" +
+				"<td><p> Row 2 </p></td>\n" + 
+				"<td><p> Row 3 </p></td>\n" +
+				"<td><p> Row 4 </p></td>\n" +
+				"<td><p> Row 5 </p></td>\n" +
+				"<td><p> Row 6 </p></td>\n" +
+				"</tr>\n" + 
+				"<tr>\n" +
+				"<th><p> :::          </p></th>\n" +
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td> Item 6 </td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> :::          </p></th>\n" +
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td><p>&nbsp;</p></td>\n" + 
+				"<td> Last 6 </td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> Header </p></th>\n" + 
+				"<td colspan='5'><p> Colspan Here </p></td>\n" +
+				"</tr>\n" + 
+				"</tbody></table>\n" + 
+				"";
+		actual = tester.convertColspans(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
 
+
+	
+	public void testConvertMoreRowSpanTroubles() {
+		String input, expected, actual;
+		input = "<table><tbody>\n" + 
+				"<tr>\n" + 
+				"<th><p> HEADER         ::UWCTOKENCOLSPANS:6::</p></th>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> h1       </p></th>\n" + 
+				"<th><p> h2                                                </p></th>\n" + 
+				"<th><p> h3                             </p></th>\n" + 
+				"<th><p> h4                                        </p></th>\n" + 
+				"<th><p> h5                        </p></th>\n" + 
+				"<th><p> h6        </p></th>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<td><p> r1c1        ::UWCTOKENROWSPANS:3::</p></td>\n" + 
+				"<td><p> r1c2     </p></td>\n" + 
+				"<td><p> r1c3     ::UWCTOKENROWSPANS:3::</p></td>\n" + 
+				"<td><p> r1c4  </p></td>\n" + 
+				"<td><p> r1c5  ::UWCTOKENROWSPANS:3::</p></td>\n" + 
+				"<td><p> r1c6  ::UWCTOKENROWSPANS:3::</p></td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<td><p> :::           </p></td>\n" + 
+				"<td><p> r2c2          </p></td>\n" + 
+				"<td><p> :::                                       </p></td>\n" + 
+				"<td><p> r2c4        </p></td>\n" + 
+				"<td><p> :::                               </p></td>\n" + 
+				"<td><p> :::             </p></td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<td><p> :::           </p></td>\n" + 
+				"<td><p> r3c2          </p></td>\n" + 
+				"<td><p> :::                                       </p></td>\n" + 
+				"<td><p>r3c4</p></td>\n" + 
+				"<td><p> :::                               </p></td>\n" + 
+				"<td><p> :::             </p></td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<td><p> r4c1         ::UWCTOKENROWSPANS:3::</p></td>\n" + 
+				"<td><p> r4c2     </p></td>\n" + 
+				"<td><p> r4c3     ::UWCTOKENROWSPANS:3::</p></td>\n" + 
+				"<td><p> r4c4  </p></td>\n" + 
+				"<td><p> r4c5  ::UWCTOKENROWSPANS:3::</p></td>\n" + 
+				"<td><p> r4c6  ::UWCTOKENROWSPANS:3::</p></td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<td><p> :::           </p></td>\n" + 
+				"<td><p> r2c2          </p></td>\n" + 
+				"<td><p> :::                                       </p></td>\n" + 
+				"<td><p> r2c4        </p></td>\n" + 
+				"<td><p> :::                               </p></td>\n" + 
+				"<td><p> :::             </p></td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<td><p> :::           </p></td>\n" + 
+				"<td><p> r3c2          </p></td>\n" + 
+				"<td><p> :::                                       </p></td>\n" + 
+				"<td><p>r3c4</p></td>\n" + 
+				"<td><p> :::                               </p></td>\n" + 
+				"<td><p> :::             </p></td>\n" + 
+				"</tr>\n" + 
+				"</table>\n" + 
+				"";
+		expected = "<table><tbody>\n" + 
+				"<tr>\n" + 
+				"<th colspan='6'><p> HEADER         </p></th>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<th><p> h1       </p></th>\n" + 
+				"<th><p> h2                                                </p></th>\n" + 
+				"<th><p> h3                             </p></th>\n" + 
+				"<th><p> h4                                        </p></th>\n" + 
+				"<th><p> h5                        </p></th>\n" + 
+				"<th><p> h6        </p></th>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<td rowspan='3'><p> r1c1        </p></td>\n" + 
+				"<td><p> r1c2     </p></td>\n" + 
+				"<td rowspan='3'><p> r1c3     </p></td>\n" + 
+				"<td><p> r1c4  </p></td>\n" + 
+				"<td rowspan='3'><p> r1c5  </p></td>\n" + 
+				"<td rowspan='3'><p> r1c6  </p></td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" +
+				"\n" + 
+				"<td><p> r2c2          </p></td>\n" +
+				"\n" +
+				"<td><p> r2c4        </p></td>\n" +
+				"\n" +
+				"\n" +
+				"</tr>\n" + 
+				"<tr>\n" +
+				"\n" +
+				"<td><p> r3c2          </p></td>\n" +
+				"\n" +
+				"<td><p>r3c4</p></td>\n" +
+				"\n" +
+				"\n" +
+				"</tr>\n" + 
+				"<tr>\n" + 
+				"<td rowspan='3'><p> r4c1         </p></td>\n" + 
+				"<td><p> r4c2     </p></td>\n" + 
+				"<td rowspan='3'><p> r4c3     </p></td>\n" + 
+				"<td><p> r4c4  </p></td>\n" + 
+				"<td rowspan='3'><p> r4c5  </p></td>\n" + 
+				"<td rowspan='3'><p> r4c6  </p></td>\n" + 
+				"</tr>\n" + 
+				"<tr>\n" +
+				"\n" + 
+				"<td><p> r2c2          </p></td>\n" +
+				"\n" +
+				"<td><p> r2c4        </p></td>\n" +
+				"\n" +
+				"\n" +
+				"</tr>\n" + 
+				"<tr>\n" +
+				"\n" +
+				"<td><p> r3c2          </p></td>\n" +
+				"\n" +
+				"<td><p>r3c4</p></td>\n" +
+				"\n" +
+				"\n" +
+				"</tr>\n" + 
+				"</table>\n";
+		Page page = new Page(null);
+		page.setOriginalText(input);
+		tester.convert(page);
+		actual = page.getConvertedText();
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
 }
diff --git a/src/com/atlassian/uwc/hierarchies/ContentHierarchyTest.java b/src/com/atlassian/uwc/hierarchies/ContentHierarchyTest.java
index 71564f6..9095c8f 100644
--- a/src/com/atlassian/uwc/hierarchies/ContentHierarchyTest.java
+++ b/src/com/atlassian/uwc/hierarchies/ContentHierarchyTest.java
@@ -4,9 +4,8 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import java.util.Collection;
 import java.util.Properties;
-import java.util.Set;
 import java.util.Vector;
 
 import junit.framework.TestCase;
@@ -49,13 +48,13 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("Plants"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(2, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -74,7 +73,7 @@
 			treenode = nodes2.get(1);
 		}
 		
-		Set<HierarchyNode> treechildren = treenode.getChildren();
+		Collection<HierarchyNode> treechildren = treenode.getChildren();
 		assertEquals(2, treechildren.size());
 		Vector<HierarchyNode> trees = new Vector<HierarchyNode>();
 		trees.addAll(treechildren);
@@ -83,7 +82,7 @@
 		assertTrue((trees.get(1)).getName().equals("Chestnut") ||
 				(trees.get(1)).getName().equals("Pine"));
 		
-		Set<HierarchyNode> flowerchildren = flowernode.getChildren();
+		Collection<HierarchyNode> flowerchildren = flowernode.getChildren();
 		assertEquals(3, flowerchildren.size());
 		Vector<HierarchyNode> flowers = new Vector<HierarchyNode>();
 		flowers.addAll(flowerchildren);
@@ -128,19 +127,19 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> home = actual.getChildren();
+		Collection<HierarchyNode> home = actual.getChildren();
 		assertEquals(1, home.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(home);
 		assertTrue((nodes0.get(0)).getName().equals("Home"));
 		
-		Set<HierarchyNode> children1 = nodes0.get(0).getChildren();
+		Collection<HierarchyNode> children1 = nodes0.get(0).getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("Plants"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(2, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -159,7 +158,7 @@
 			treenode = nodes2.get(1);
 		}
 		
-		Set<HierarchyNode> treechildren = treenode.getChildren();
+		Collection<HierarchyNode> treechildren = treenode.getChildren();
 		assertEquals(2, treechildren.size());
 		Vector<HierarchyNode> trees = new Vector<HierarchyNode>();
 		trees.addAll(treechildren);
@@ -168,7 +167,7 @@
 		assertTrue((trees.get(1)).getName().equals("Chestnut") ||
 				(trees.get(1)).getName().equals("Pine"));
 		
-		Set<HierarchyNode> flowerchildren = flowernode.getChildren();
+		Collection<HierarchyNode> flowerchildren = flowernode.getChildren();
 		assertEquals(3, flowerchildren.size());
 		Vector<HierarchyNode> flowers = new Vector<HierarchyNode>();
 		flowers.addAll(flowerchildren);
@@ -208,13 +207,13 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("Plants"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(3, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -232,7 +231,7 @@
 			if (node.getName().equals("Plants_Nuts")) nutnode = node;
 		}
 		
-		Set<HierarchyNode> treechildren = treenode.getChildren();
+		Collection<HierarchyNode> treechildren = treenode.getChildren();
 		assertEquals(2, treechildren.size());
 		Vector<HierarchyNode> trees = new Vector<HierarchyNode>();
 		trees.addAll(treechildren);
@@ -241,7 +240,7 @@
 		assertTrue((trees.get(1)).getName().equals("Plants_Trees_Chestnut") ||
 				(trees.get(1)).getName().equals("Plants_Trees_Pine"));
 		
-		Set<HierarchyNode> flowerchildren = flowernode.getChildren();
+		Collection<HierarchyNode> flowerchildren = flowernode.getChildren();
 		assertEquals(3, flowerchildren.size());
 		Vector<HierarchyNode> flowers = new Vector<HierarchyNode>();
 		flowers.addAll(flowerchildren);
@@ -255,7 +254,7 @@
 				(flowers.get(2)).getName().equals("Plants_Flowers_Daisy") ||
 				(flowers.get(2)).getName().equals("Plants_Flowers_Orchid"));
 		
-		Set<HierarchyNode> nutchildren = nutnode.getChildren();
+		Collection<HierarchyNode> nutchildren = nutnode.getChildren();
 		assertEquals(2, nutchildren.size());
 		Vector<HierarchyNode> nuts = new Vector<HierarchyNode>();
 		nuts.addAll(nutchildren);
@@ -296,13 +295,13 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("Plants"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(3, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -320,7 +319,7 @@
 			if (node.getName().equals("Nuts")) nutnode = node;
 		}
 		
-		Set<HierarchyNode> treechildren = treenode.getChildren();
+		Collection<HierarchyNode> treechildren = treenode.getChildren();
 		assertEquals(2, treechildren.size());
 		Vector<HierarchyNode> trees = new Vector<HierarchyNode>();
 		trees.addAll(treechildren);
@@ -329,7 +328,7 @@
 		assertTrue((trees.get(1)).getName().equals("Chestnut") ||
 				(trees.get(1)).getName().equals("Pine"));
 		
-		Set<HierarchyNode> flowerchildren = flowernode.getChildren();
+		Collection<HierarchyNode> flowerchildren = flowernode.getChildren();
 		assertEquals(3, flowerchildren.size());
 		Vector<HierarchyNode> flowers = new Vector<HierarchyNode>();
 		flowers.addAll(flowerchildren);
@@ -343,7 +342,7 @@
 				(flowers.get(2)).getName().equals("Daisy") ||
 				(flowers.get(2)).getName().equals("Orchid"));
 		
-		Set<HierarchyNode> nutchildren = nutnode.getChildren();
+		Collection<HierarchyNode> nutchildren = nutnode.getChildren();
 		assertEquals(2, nutchildren.size());
 		Vector<HierarchyNode> nuts = new Vector<HierarchyNode>();
 		nuts.addAll(nutchildren);
@@ -376,13 +375,13 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("Plants"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(2, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -397,7 +396,7 @@
 			if (node.getName().equals("Plants Nuts")) nutnode = node;
 		}
 		
-		Set<HierarchyNode> flowerchildren = flowernode.getChildren();
+		Collection<HierarchyNode> flowerchildren = flowernode.getChildren();
 		assertEquals(2, flowerchildren.size());
 		Vector<HierarchyNode> flowers = new Vector<HierarchyNode>();
 		flowers.addAll(flowerchildren);
@@ -406,7 +405,7 @@
 		assertTrue((flowers.get(1)).getName().equals("Plants Flowers Rose") ||
 				(flowers.get(1)).getName().equals("Plants Flowers Orchid"));
 		
-		Set<HierarchyNode> nutchildren = nutnode.getChildren();
+		Collection<HierarchyNode> nutchildren = nutnode.getChildren();
 		assertEquals(1, nutchildren.size());
 		Vector<HierarchyNode> nuts = new Vector<HierarchyNode>();
 		nuts.addAll(nutchildren);
@@ -447,13 +446,13 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("Plants"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(1, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -461,7 +460,7 @@
 
 		HierarchyNode treenode = nodes2.get(0); 
 		
-		Set<HierarchyNode> treechildren = treenode.getChildren();
+		Collection<HierarchyNode> treechildren = treenode.getChildren();
 		assertEquals(5, treechildren.size());
 		Vector<HierarchyNode> trees = new Vector<HierarchyNode>();
 		trees.addAll(treechildren);
@@ -518,19 +517,19 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> home = actual.getChildren();
+		Collection<HierarchyNode> home = actual.getChildren();
 		assertEquals(1, home.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(home);
 		assertTrue((nodes0.get(0)).getName().equals("Home"));
 		
-		Set<HierarchyNode> children1 = nodes0.get(0).getChildren();
+		Collection<HierarchyNode> children1 = nodes0.get(0).getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("Plants"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(1, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -538,7 +537,7 @@
 
 		HierarchyNode treenode = nodes2.get(0); 
 		
-		Set<HierarchyNode> treechildren = treenode.getChildren();
+		Collection<HierarchyNode> treechildren = treenode.getChildren();
 		assertEquals(5, treechildren.size());
 		Vector<HierarchyNode> trees = new Vector<HierarchyNode>();
 		trees.addAll(treechildren);
@@ -593,7 +592,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
@@ -631,13 +630,13 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("Plants"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(1, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -645,7 +644,7 @@
 
 		HierarchyNode treenode = nodes2.get(0); 
 		
-		Set<HierarchyNode> treechildren = treenode.getChildren();
+		Collection<HierarchyNode> treechildren = treenode.getChildren();
 		assertEquals(5, treechildren.size());
 		Vector<HierarchyNode> trees = new Vector<HierarchyNode>();
 		trees.addAll(treechildren);
@@ -808,13 +807,13 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
 		assertTrue((nodes1.get(0)).getName().equals("TestPage"));
 
-		Set<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
+		Collection<HierarchyNode> children2 = (nodes1.get(0)).getChildren();
 		assertEquals(3, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -824,7 +823,7 @@
 
 		HierarchyNode parent0node = nodes2.get(2); 
 		
-		Set<HierarchyNode> p0children = parent0node.getChildren();
+		Collection<HierarchyNode> p0children = parent0node.getChildren();
 		assertEquals(2, p0children.size());
 		Vector<HierarchyNode> p0 = new Vector<HierarchyNode>();
 		p0.addAll(p0children);
diff --git a/src/com/atlassian/uwc/hierarchies/DocDirectoryHierarchyTest.java b/src/com/atlassian/uwc/hierarchies/DocDirectoryHierarchyTest.java
index adb7d14..bb09e8d 100644
--- a/src/com/atlassian/uwc/hierarchies/DocDirectoryHierarchyTest.java
+++ b/src/com/atlassian/uwc/hierarchies/DocDirectoryHierarchyTest.java
@@ -1,8 +1,8 @@
 package com.atlassian.uwc.hierarchies;
 
 import java.io.File;
+import java.util.Collection;
 import java.util.Properties;
-import java.util.Set;
 import java.util.Vector;
 
 import junit.framework.TestCase;
@@ -10,7 +10,6 @@
 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;
 
-import com.atlassian.uwc.filters.NoSvnFilter;
 import com.atlassian.uwc.ui.Page;
 
 public class DocDirectoryHierarchyTest extends TestCase {
@@ -49,7 +48,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 		
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
@@ -60,7 +59,7 @@
 		File file = (File) object;
 		assertTrue("test.txt".equals(file.getName()));
 		
-		Set<HierarchyNode> children2 = nodes1.get(0).getChildren();
+		Collection<HierarchyNode> children2 = nodes1.get(0).getChildren();
 		assertEquals(3, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
@@ -76,7 +75,7 @@
 				assertTrue("test1.txt".equals(filea.getName())||
 						"test2.txt".equals(filea.getName())
 						);
-				Set<HierarchyNode> children3 = node.getChildren();
+				Collection<HierarchyNode> children3 = node.getChildren();
 				assertEquals(1, children3.size());
 				Vector<HierarchyNode> nodes3 = new Vector<HierarchyNode>();
 				nodes3.addAll(children3);
@@ -123,7 +122,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 		
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
@@ -152,7 +151,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 		
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
@@ -183,7 +182,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 		
-		Set<HierarchyNode> children1 = actual.getChildren();
+		Collection<HierarchyNode> children1 = actual.getChildren();
 		assertEquals(1, children1.size());
 		Vector<HierarchyNode> nodes1 = new Vector<HierarchyNode>();
 		nodes1.addAll(children1);
@@ -194,7 +193,7 @@
 		File file = (File) object;
 		assertTrue("test.txt".equals(file.getName()));
 		
-		Set<HierarchyNode> children2 = nodes1.get(0).getChildren();
+		Collection<HierarchyNode> children2 = nodes1.get(0).getChildren();
 		assertEquals(2, children2.size());
 		Vector<HierarchyNode> nodes2 = new Vector<HierarchyNode>();
 		nodes2.addAll(children2);
diff --git a/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchy.java b/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchy.java
index 512c1d1..7479af6 100644
--- a/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchy.java
+++ b/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchy.java
@@ -2,6 +2,7 @@
 
 import java.io.File;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.Properties;
 import java.util.Set;
@@ -9,10 +10,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import sun.tools.tree.AddExpression;
-
 import com.atlassian.uwc.converters.dokuwiki.HierarchyTitleConverter;
-import com.atlassian.uwc.converters.tikiwiki.RegexUtil;
 import com.atlassian.uwc.filters.NoSvnFilter;
 import com.atlassian.uwc.ui.Page;
 
@@ -21,20 +19,35 @@
 	int newpagescount = 0;
 	
 	public HierarchyNode buildHierarchy(Collection<Page> pages) {
+//		getRootNode().setChildrenComparator(dokuComparator());
+		getRootNode().childrenAsList(true);
 		log.debug("Number of hierarchy pages: " + pages.size());
 		//run the filepath hierarchy first -
 		HierarchyNode node = super.buildHierarchy(pages);
+//		log.debug("filepath hierarchy tree:");
+//		printTree(node);
 		//move spacekeys
-		node = handleSpacekeyBranchWithProp(node);
-		//handle start pages
-		node = handleHomePages(node, true);
+//		node = handleSpacekeyBranchWithProp(node);
 		//handle spacekeys not with a property, but with page.getSpacekey setting
 		node = handleSpacekeyBranchWithPage(node);
+//		log.debug("branching by spacekey:");
+//		printTree(node);
+		//handle start pages
+		node = handleHomePages(node, true);
+//		log.debug("merging home pages:");
+//		printTree(node);
 		
 		//fix collisions
 		node = fixCollisions(node);
+//		log.debug("fix collisions:");
+//		printTree(node);
+		//fix broken branches
+		node = mergeBrokenNodes(node);
+//		log.debug("merge broken nodes:");
+//		printTree(node);
 		//fix titles
 		node = fixTitles(node);
+//		log.debug("fix titles:");
 		//attach images - old attachment handling. Idea is to put the attachments in the 
 		//new confluence page corresponding with the media directory for that namespace
 		//but this doesn't work if the media dir doesn't correspond to an existing pages namespace
@@ -53,10 +66,12 @@
 	}
 
 
+
+
 	private HierarchyNode handleSpacekeyBranchWithProp(HierarchyNode node) {
 		String spacekey = getProperties().getProperty("spacekey");
 		if (spacekey != null && !"".equals(spacekey)) { 
-			Set<HierarchyNode> top = node.getChildren();
+			Collection<HierarchyNode> top = node.getChildren();
 			for (Iterator iter = top.iterator(); iter.hasNext();) {
 				HierarchyNode topnode = (HierarchyNode) iter.next();
 				if (topnode.getName().toLowerCase().equals(spacekey.toLowerCase())) {
@@ -70,7 +85,7 @@
 	}
 
 	private HierarchyNode handleSpacekeyBranchWithPage(HierarchyNode root) {
-		Set<HierarchyNode> top = root.getChildren();
+		Collection<HierarchyNode> top = root.getChildren();
 		Vector<HierarchyNode> ordered = new Vector<HierarchyNode>(top);
 		for (HierarchyNode topnode : ordered) {
 			root = handleSpacekeyBranchTop(root, topnode);
@@ -85,27 +100,8 @@
 		else if (node.getPage().getSpacekey() != null) {
 			String spacekey = node.getPage().getSpacekey();
 			node = setAncestorsBySpacekey(root, node, spacekey);
-			if (node.getParent() == null) {
-				Set<HierarchyNode> top = root.getChildren();
-				Vector<HierarchyNode> ordered = new Vector<HierarchyNode>(top);
-				for (HierarchyNode topnode : ordered) {
-					if (topnode.getPage() == null) {
-						log.debug("NULL! (Skipping) topnode.getName() = " + topnode.getName());
-						continue;
-					}
-					if (spacekey.equals(topnode.getPage().getSpacekey())) {
-						Set<HierarchyNode> children = topnode.getChildren();
-						log.debug("Moving topnode: " + topnode.getPage().getName());
-						topnode.setParent(null); //since we have to use iter.remove instead of node.removeChild(topnode)
-						for (HierarchyNode child : children) {
-							root.addChild(child);
-						}
-						break;
-					}
-				}
-			}
 		}
-		Set<HierarchyNode> nextSet = node.getChildren();
+		Collection<HierarchyNode> nextSet = node.getChildren();
 		Vector<HierarchyNode> ordered = new Vector<HierarchyNode>(nextSet);
 		for (HierarchyNode next : ordered) {
 			root = handleSpacekeyBranchTop(root, next);
@@ -138,17 +134,56 @@
 		}
 		else if (!parent.getPage().getSpacekey().equals(spacekey)) {
 			log.debug("...parent.getPage().getSpacekey: " + parent.getPage().getSpacekey() + "... and spacekey: " + spacekey);
-			log.debug("Copying branch to new parent because of spacekey: " + node.getName());
-
-			copyBranch(node, spacekey, parent);
-			
+			HierarchyNode branch = hasSpaceBranchAlready(parent, spacekey);
+			if (branch != null) {
+				log.debug("Moving node ('"+node.getName()+"') to existing branch ('"+branch.getName()+"') because of spacekey: " + spacekey);
+				moveBranch(node, parent, branch);
+			}
+			else {
+				log.debug("Copying branch to new parent because of spacekey: " + node.getName());
+				copyBranch(node, spacekey, parent);
+			}
+//			log.debug("Printing the whole tree after copy/move");
+//			printTree(root);
 		}
 		return node;
 	}
 
 
+	private HierarchyNode hasSpaceBranchAlready(HierarchyNode parent, String spacekey) {
+		HierarchyNode current = parent;
+		int level = 0;
+		while (current.getPage() != null) {
+			current = current.getParent();
+			for (HierarchyNode child : current.getChildren()) {
+				String childspace = (child.getPage() == null)?"":child.getPage().getSpacekey();
+				if (childspace.equalsIgnoreCase(spacekey)) {
+					HierarchyNode depthNode = child;
+					while (level-- > 0) {
+						if (depthNode.getChildren().size()>0) {
+							Vector<HierarchyNode> onenode = new Vector<HierarchyNode>(depthNode.getChildren());
+							depthNode = onenode.firstElement();
+						}
+					}
+					return depthNode;
+				}
+			}
+			level++;
+		}
+		return null;
+	}
+
+
+	private void moveBranch(HierarchyNode node, HierarchyNode parent, HierarchyNode branch) {
+		log.debug("node: " + node.getName() + " parent: " + parent.getName() + " branch: " + branch.getName());
+		parent.removeChild(node);
+		branch.addChild(node);
+	}
+
+
 	public void copyBranch(HierarchyNode node, String spacekey,
 			HierarchyNode parent) {
+		log.debug("node: " + node.getName() + " parent: " + parent.getName());
 		HierarchyNode newparent = new HierarchyNode();
 		newparent.setName(parent.getName());
 		Page newparentpage = createPage(parent.getName());
@@ -158,15 +193,19 @@
 		if (parent.getPage().getConvertedText() != null) 
 			newparentpage.setConvertedText(parent.getPage().getConvertedText());
 		newparent.setPage(newparentpage);
-		parent.removeChild(node);
+		HierarchyNode gparent = parent.getParent();
+		if (!parent.removeChild(node)) {
+//			log.debug("tree for parent: " + parent.getName());
+//			printTree(parent);
+		}
 		newparent.addChild(node);
-		parent.getParent().addChild(newparent);
-		if (parent.getParent().getName() == null) return;
-		copyBranch(newparent, spacekey, parent.getParent());
+		gparent.addChild(newparent);
+		if (gparent.getName() == null) return;
+		copyBranch(newparent, spacekey, gparent);
 	}
 
 	private void setTopNodeBranch(HierarchyNode root, Iterator topiter, HierarchyNode nexttopnode) {
-		Set<HierarchyNode> children = nexttopnode.getChildren();
+		Collection<HierarchyNode> children = nexttopnode.getChildren();
 		topiter.remove(); //Only allowed way to remove from an iterator. 
 		nexttopnode.setParent(null); //since we have to use iter.remove instead of node.removeChild(topnode)
 		for (HierarchyNode child : children) {
@@ -175,7 +214,7 @@
 	}
 
 	private HierarchyNode handleHomePages(HierarchyNode node, boolean top) {
-		Set<HierarchyNode> children = node.getChildren();
+		Collection<HierarchyNode> children = node.getChildren();
 		for (Iterator iter = children.iterator(); iter.hasNext();) { 
 			HierarchyNode child = (HierarchyNode) iter.next();
 			String name = child.getName();
@@ -201,17 +240,19 @@
 
 	private HierarchyNode fixCollisions(HierarchyNode node) {
 		
-		Set<HierarchyNode> children = node.getChildren();
-		for (Iterator iter = children.iterator(); iter.hasNext();) { 
-			HierarchyNode child = (HierarchyNode) iter.next();
+		Collection<HierarchyNode> children = node.getChildren();
+		Vector<HierarchyNode> childrenV = new Vector<HierarchyNode>(children);
+		for (int i = 0; i < childrenV.size(); i++) {
+			HierarchyNode child = (HierarchyNode) childrenV.get(i);
 			
 			Vector<String> collisions = getCollisionsForThisNode(child);
-			for (String name : collisions) {
+			for (int j = 0; j < collisions.size(); j++) {
+				String name = collisions.get(j);
 				String eqname = equalize(name);
 				String childname = equalize(child.getName());
 				if (childname.equals(eqname)) {
 					log.debug("Examining collisions candidate: '" + eqname + "' for this child: '" + childname + "'");
-					String parent = child.getParent().getName();
+					String parent = getParentTitle(child);
 					log.debug("parent = " + parent);
 					if (parent == null) continue;
 					String newname = parent + " " + child.getName();
@@ -224,7 +265,114 @@
 		}
 		return node;
 	}
+	
+	private static int numericCollisionCounter = 2;
+	private HierarchyNode mergeBrokenNodes(HierarchyNode node) {
+		
+		Collection<HierarchyNode> children = node.getChildren();
+		Vector<HierarchyNode> childrenV = new Vector<HierarchyNode>(children);
+		for (int i = 0; i < childrenV.size(); i++) {
+			HierarchyNode child = (HierarchyNode) childrenV.get(i);
+			log.debug("Merging child: '"  + child.getName()+"'");
+			HierarchyNode parent = child.getParent();
+			if (child.getPage() != null && child.getPage().getFile() != null) { 
+				Collection<HierarchyNode> siblingsSet = parent.getChildren();
+				Vector<HierarchyNode> siblings = new Vector<HierarchyNode>();
+				for (HierarchyNode s : siblingsSet) {
+					if (!s.equals(child)) {
+						siblings.add(s);
+					}
+				}
+//				log.debug("sibling size: " + siblings.size());
+				for (int j = 0; j < siblings.size(); j++) {
+					HierarchyNode sibling = siblings.get(j);
+//					log.debug("all siblings: " + sibling.getName());
+					if (sibling.getPage() == null || sibling.getPage().getFile() == null) {
+						if (compareNodes(child, sibling)) {
+							mergeTwoNodes(child, sibling, parent);
+						}
+					}
+					else if (child.getName().equalsIgnoreCase(sibling.getName())) {
+						String newname = sibling.getName() + " " + numericCollisionCounter++;
+						log.debug("Found conflicting node. Changing name to: '"+ newname + "'");
+						sibling.setName(newname);
+						sibling.getPage().setName(newname);
+					}
+				}
+			}
+//			log.debug("***v");
+			child = mergeBrokenNodes(child);
+		}
 
+		return node;
+	}
+
+
+	protected void mergeTwoNodes(HierarchyNode first, HierarchyNode second,
+			HierarchyNode parent) {
+		log.debug("Merging second node (sibling): '" + second.getName()+"'");
+		for (HierarchyNode gchild : second.getChildren()) {
+//			log.debug("adding child: " + gchild.getName());
+			first.addChild(gchild);
+		}
+		if (parent != null) {
+			if (!parent.removeChild(second)) {
+//				log.debug("parent tree");
+//				printTree(parent);
+			}
+		}
+//		log.debug("child tree");
+//		printTreeOneLevel(first);
+	}
+
+
+	/**
+	 * Uses the filepath of the first node to compare names with the second node.
+	 * Note, these nodes should already be at the same depth and have the same parent before
+	 * calling this method.
+	 * @param first should have a page with a file object
+	 * @param second must have node name, but doesn't need page 
+	 * @return if they are the same node
+	 */
+	protected boolean compareNodes(HierarchyNode first, HierarchyNode second) {
+		String filename = first.getPage().getFile().getName();
+		filename = filename.replaceFirst("\\.txt$", "");
+		boolean samenode = equalize(second.getName()).equals(equalize(filename));
+		if (samenode) {
+			log.debug("first node name: " + first.getName() + " and full file path: " + first.getPage().getFile().getAbsolutePath());
+			log.debug("filename of first node: " + filename + " and equalized: " + equalize(filename));
+			log.debug("second node name: " + second.getName() + " and equalized: " + equalize(second.getName()));
+			log.debug("first spacekey " + first.getPage().getSpacekey() + " ... second spacekey " + ((second.getPage()!=null)?second.getPage().getSpacekey():"null"));
+		}
+		return samenode;
+	}
+
+	protected String getParentTitle(HierarchyNode child) {
+		String basename = child.getParent().getName(); 
+		//unchanged source is set in the engine. 
+		//if it's null, we created the parent in the hierarchy, so we need to review the title
+		if (child.getParent().getPage() != null && child.getParent().getPage().getUnchangedSource() != null) 
+			return child.getParent().getPage().getName();
+		log.debug("Attempting to identify meta name for parent title.");
+		HierarchyNode gparent = child.getParent().getParent();
+		if (gparent == null) return basename;
+		Collection<HierarchyNode> siblingsSet = gparent.getChildren();
+		Vector<HierarchyNode> siblings = new Vector<HierarchyNode>(siblingsSet);
+		for (int i = 0; i < siblings.size(); i++) {
+			HierarchyNode sibling = (HierarchyNode) siblings.get(i);
+			if (sibling.getPage() != null && sibling.getPage().getFile() != null) {
+//				log.debug("Examining sibling: "+ sibling.getName());
+				String filename = sibling.getPage().getFile().getName();
+				filename = filename.replaceFirst("\\.txt$", "");
+				if (equalize(basename).equals(equalize(filename))) {
+					log.debug("returning sibling name: " + sibling.getName());
+					return sibling.getName();
+				}
+			}
+		}
+		return basename;
+	}
+	
 	public Vector<String> getCollisionsForThisNode(HierarchyNode node) {
 		String spacekey = (node.getPage() != null && node.getPage().getSpacekey() != null)?
 				node.getPage().getSpacekey() :
@@ -235,12 +383,12 @@
 	protected Vector<String> getCollisionsCandidates(String spacekey) {
 		Properties props = getProperties();
 		Vector<String> candidates = new Vector<String>();
-		log.debug("Looking for collisions candidates for spacekey: " + spacekey);
+//		log.debug("Looking for collisions candidates for spacekey: " + spacekey);
 		for (Iterator iter = props.keySet().iterator();iter.hasNext();) {
 			String key = (String) iter.next();
 			if (key.toLowerCase().startsWith("collision-titles-"+spacekey.toLowerCase())) {
 				String namesraw = props.getProperty(key, "");
-				log.debug("Found collisions data: " + namesraw);
+//				log.debug("Found collisions data: " + namesraw);
 				if ("".equals(namesraw)) continue;
 				String[] names = namesraw.split(",");
 				for (String name : names) {
@@ -249,12 +397,12 @@
 				}
 			}
 		}
-		log.debug("candidates size? " + candidates.size());
+//		log.debug("candidates size? " + candidates.size());
 		return candidates;
 	}
 
 	private HierarchyNode attachAllImages(HierarchyNode node, Vector<File> attdirs, boolean top) {
-		Set<HierarchyNode> children = node.getChildren();
+		Collection<HierarchyNode> children = node.getChildren();
 		for (Iterator iter = children.iterator(); iter.hasNext();) { 
 			HierarchyNode child = (HierarchyNode) iter.next();
 			String name = getOrigChildName(child);
@@ -322,13 +470,14 @@
 	}
 
 	private Page createPage(String name) {
-		 Page page = new Page(null);
-         page.setName(name);
-         page.setOriginalText("");
-         page.setConvertedText("");
-         page.setPath(name);
-         newpagescount++;
-         return page;
+		log.debug("Creating page in hierarchy for: " + name);
+		Page page = new Page(null);
+		page.setName(name);
+		page.setOriginalText("");
+		page.setConvertedText("");
+		page.setPath(name);
+		newpagescount++;
+		return page;
 	}
 
 	NoSvnFilter svnfilter = new NoSvnFilter();
@@ -356,7 +505,7 @@
 	HierarchyTitleConverter titleConverter = new HierarchyTitleConverter();
 	//nodes without pages probably will need this
 	private HierarchyNode fixTitles(HierarchyNode node) { 
-		Set<HierarchyNode> children = node.getChildren();
+		Collection<HierarchyNode> children = node.getChildren();
 		for (Iterator iter = children.iterator(); iter.hasNext();) { 
 			HierarchyNode child = (HierarchyNode) iter.next();
 			if (child.getPage() == null)
@@ -378,32 +527,45 @@
 	private int count;
 	@Override 
 	protected void buildRelationships(Page page, HierarchyNode root) {
+		log.debug("Building Relationship for page: " + page.getName() + " -> " + page.getFile().getName());
 		currentpage = page;
 		combineHomepageNodes = false;
 		currentParent = null;
 
 		super.buildRelationships(page, root);
 		if (combineHomepageNodes) {
-			combineHomepages(page);
+			combineHomepages();
 		}
-		log.debug("++count: "+(count++) +", completed building relationship for page: " + page.getName());
+		log.debug("++count: "+(count++) +", completed building relationship for page: " + page.getName() + " -> " + page.getFile().getName());
 	}
 
-	public void combineHomepages(Page page) {
-		Set<HierarchyNode> children = currentParent.getChildren();
+	public void combineHomepages() {
+		Collection<HierarchyNode> children = currentParent.getChildren();
 		Vector<HierarchyNode> all = new Vector<HierarchyNode>(children);
 		HierarchyNode first = null, second = null;
-		for (HierarchyNode node : all) {
-			if (node.getName().equalsIgnoreCase(page.getName())) {
-				if (first == null) first = node;
-				else second = node;
+		for (int i = 0; i < all.size()-1 && all.size() > 1; i++) {
+			first = all.get(i);
+			second = all.get(i+1); 
+			if (first == null || second == null) continue;
+			log.debug ("first = " + first.getName() + " second = " + second.getName());
+			if ((first.getPage() == null || first.getPage().getFile() == null)) {
+				if (second.getPage() != null && second.getPage().getFile() != null) {
+					HierarchyNode tmp = first;
+					first = second;
+					second = tmp;
+					log.debug ("first = " + first.getName() + " second = " + second.getName());
+				}
+				else continue;
 			}
-		}
-		if (first.getPage() == null) {
-			combineHomepages(first, second, page);
-		}
-		else { 
-			combineHomepages(second, first, page);
+			if (compareNodes(first, second)) {
+				
+				if (first.getPage() == null) {
+					combineHomepages(first, second, second.getPage());
+				}
+				else { 
+					combineHomepages(second, first, first.getPage());
+				}
+			}
 		}
 	}
 	
@@ -411,7 +573,7 @@
 			Page page) {
 		if (noChildrenNode.getPage() == null) return;//indicates this isn't the right scenario to combine
 		if (noChildrenNode.getChildren().size() > 0) {
-			log.error("Combining Homepages - noChildrenNode has children!: " + noChildrenNode.getName());
+			mergeTwoNodes(noChildrenNode, nullPageNode, noChildrenNode.getParent());
 			return;
 		}
 		log.debug("Combining: " + page.getName());
@@ -428,6 +590,7 @@
 			if (parent.getPage() == null && currentpage.getName().equalsIgnoreCase(childname)) {
 				combineHomepageNodes = true;
 				currentParent = parent;
+				log.debug("-> (dokuwikihierarchy check) false");
 				return false;
 			}
 			return hasRel;
@@ -442,11 +605,21 @@
 		printTree(node.getChildren(), "");
 	}
 
-	private void printTree(Set<HierarchyNode> children, String delim) {
+	private void printTree(Collection<HierarchyNode> children, String delim) {
 		for (HierarchyNode child : children) {
-			String newdelim = delim + " ";
-			log.debug("PRINTTREE: " + newdelim + child.getName());
+			String newdelim = delim + " .";
+			String childdata = child.getName();
+//			if (child.getPage() != null && child.getPage().getFile() != null)
+//				childdata += " -> " + child.getPage().getFile().getAbsolutePath();
+			log.debug("PRINTTREE: " + newdelim + childdata);
 			printTree(child.getChildren(), newdelim);
 		}
 	}
+	
+	private void printTreeOneLevel(HierarchyNode node) {
+		Collection<HierarchyNode> children = node.getChildren();
+		for (HierarchyNode child : children) {
+			log.debug("PRINTTREE: " + child.getName());
+		}
+	}
 }
diff --git a/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchyTest.java b/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchyTest.java
index d53f184..e46f49c 100644
--- a/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchyTest.java
+++ b/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchyTest.java
@@ -56,7 +56,7 @@
 		assertNull(root.getParent());
 		assertNotNull(root.getChildren());
 		
-		Set<HierarchyNode> top = root.getChildren();
+		Collection<HierarchyNode> top = root.getChildren();
 		assertEquals(5, top.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(top);
@@ -142,7 +142,7 @@
 		assertNull(root.getParent());
 		assertNotNull(root.getChildren());
 		
-		Set<HierarchyNode> top = root.getChildren();
+		Collection<HierarchyNode> top = root.getChildren();
 		assertEquals(5, top.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(top);
@@ -188,7 +188,7 @@
 		assertNull(root.getParent());
 		assertNotNull(root.getChildren());
 		
-		Set<HierarchyNode> top = root.getChildren();
+		Collection<HierarchyNode> top = root.getChildren();
 		assertEquals(5, top.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(top);
@@ -257,7 +257,7 @@
 		assertNull(root.getParent());
 		assertNotNull(root.getChildren());
 		
-		Set<HierarchyNode> top = root.getChildren();
+		Collection<HierarchyNode> top = root.getChildren();
 		assertEquals(3, top.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(top);
@@ -336,7 +336,7 @@
 //		assertNull(root.getParent());
 //		assertNotNull(root.getChildren());
 //		
-//		Set<HierarchyNode> top = root.getChildren();
+//		Collection<HierarchyNode> top = root.getChildren();
 //		assertEquals(6, top.size());
 //		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 //		nodes0.addAll(top);
@@ -426,7 +426,7 @@
 		assertNull(root.getParent());
 		assertNotNull(root.getChildren());
 		
-		Set<HierarchyNode> top = root.getChildren();
+		Collection<HierarchyNode> top = root.getChildren();
 		assertEquals(5, top.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(top);
@@ -479,7 +479,7 @@
 		assertNull(root.getParent());
 		assertNotNull(root.getChildren());
 		
-		Set<HierarchyNode> top = root.getChildren();
+		Collection<HierarchyNode> top = root.getChildren();
 		assertEquals(2, top.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(top);
@@ -558,7 +558,7 @@
 		assertNull(root.getParent());
 		assertNotNull(root.getChildren());
 		
-		Set<HierarchyNode> top = root.getChildren();
+		Collection<HierarchyNode> top = root.getChildren();
 		assertEquals(2, top.size());
 		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
 		nodes0.addAll(top);
diff --git a/src/com/atlassian/uwc/hierarchies/FilenameHierarchyTest.java b/src/com/atlassian/uwc/hierarchies/FilenameHierarchyTest.java
index 859b438..86cc4e8 100644
--- a/src/com/atlassian/uwc/hierarchies/FilenameHierarchyTest.java
+++ b/src/com/atlassian/uwc/hierarchies/FilenameHierarchyTest.java
@@ -1,9 +1,8 @@
 package com.atlassian.uwc.hierarchies;
 
 import java.io.File;
-import java.util.Comparator;
+import java.util.Collection;
 import java.util.Properties;
-import java.util.Set;
 import java.util.Vector;
 
 import junit.framework.TestCase;
@@ -39,7 +38,7 @@
 		HierarchyNode actual = tester.buildHierarchy(pages);
 		assertNotNull(actual);
 		
-		Set<HierarchyNode> nodes = actual.getChildren();
+		Collection<HierarchyNode> nodes = actual.getChildren();
 		assertNotNull(nodes);
 		assertEquals(1, nodes.size());
 		for (HierarchyNode root : nodes) {
@@ -62,7 +61,7 @@
 		HierarchyNode actual = tester.buildHierarchy(pages);
 		assertNotNull(actual);
 		
-		Set<HierarchyNode> nodes = actual.getChildren();
+		Collection<HierarchyNode> nodes = actual.getChildren();
 		assertNotNull(nodes);
 		assertEquals(1, nodes.size());
 		for (HierarchyNode root : nodes) {
@@ -89,7 +88,7 @@
 		HierarchyNode actual = tester.buildHierarchy(pages);
 		assertNotNull(actual);
 		
-		Set<HierarchyNode> nodes = actual.getChildren();
+		Collection<HierarchyNode> nodes = actual.getChildren();
 		assertNotNull(nodes);
 		assertEquals(1, nodes.size());
 		for (HierarchyNode root : nodes) {
@@ -122,7 +121,7 @@
 		HierarchyNode actual = tester.buildHierarchy(pages);
 		assertNotNull(actual);
 		
-		Set<HierarchyNode> nodes = actual.getChildren();
+		Collection<HierarchyNode> nodes = actual.getChildren();
 		assertNotNull(nodes);
 		assertEquals(1, nodes.size());
 		for (HierarchyNode root : nodes) {
@@ -244,7 +243,7 @@
 		pages.add(page);
 	}
 	
-	private Vector<HierarchyNode> asVector(Set<HierarchyNode> children) {
+	private Vector<HierarchyNode> asVector(Collection<HierarchyNode> children) {
 		Vector<HierarchyNode> nodes = new Vector<HierarchyNode>();
 		nodes.addAll(children);
 		return nodes;
diff --git a/src/com/atlassian/uwc/hierarchies/FilepathHierarchy.java b/src/com/atlassian/uwc/hierarchies/FilepathHierarchy.java
index 6102e31..95a840e 100644
--- a/src/com/atlassian/uwc/hierarchies/FilepathHierarchy.java
+++ b/src/com/atlassian/uwc/hierarchies/FilepathHierarchy.java
@@ -173,7 +173,7 @@
 		log.debug("...... checking parent '" + parent.getName() + "' has relationship with '" + childname + "'");
 		boolean relationship = false;
 		
-		Set<HierarchyNode> children = parent.getChildren();
+		Collection<HierarchyNode> children = parent.getChildren();
 		if (children == null || children.isEmpty()) {
 			log.debug("...... -> parent has no children");
 			return false;
@@ -226,7 +226,7 @@
 	 */
 	protected HierarchyNode getChildNode(HierarchyNode parent, String childname) {
 		log.debug("...... Getting child node with name: " + childname);
-		Set<HierarchyNode> children = parent.getChildren();
+		Collection<HierarchyNode> children = (Collection<HierarchyNode>) parent.getChildren();
 		if (children == null || children.isEmpty()) {
 			log.debug("...... -> parent has no children. Returning null.");
 			return null;
diff --git a/src/com/atlassian/uwc/hierarchies/FilepathHierarchyTest.java b/src/com/atlassian/uwc/hierarchies/FilepathHierarchyTest.java
index 7214176..9ef0f54 100644
--- a/src/com/atlassian/uwc/hierarchies/FilepathHierarchyTest.java
+++ b/src/com/atlassian/uwc/hierarchies/FilepathHierarchyTest.java
@@ -5,7 +5,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Properties;
-import java.util.Set;
 import java.util.Vector;
 
 import junit.framework.TestCase;
@@ -13,7 +12,6 @@
 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;
 
-import com.atlassian.uwc.converters.jotspot.TitleConverter;
 import com.atlassian.uwc.filters.NoSvnFilter;
 import com.atlassian.uwc.ui.Page;
 
@@ -80,7 +78,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 		
-		Set<HierarchyNode> level1ChildSet = actual.getChildren();
+		Collection<HierarchyNode> level1ChildSet = actual.getChildren();
 		assertEquals(2, level1ChildSet.size());
 		Vector<HierarchyNode> level1Children = new Vector<HierarchyNode>();
 		level1Children.addAll(level1ChildSet);
@@ -127,7 +125,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 		
-		Set<HierarchyNode> level1ChildSet = actual.getChildren();
+		Collection<HierarchyNode> level1ChildSet = actual.getChildren();
 		assertEquals(2, level1ChildSet.size());
 		Vector<HierarchyNode> level1Children = new Vector<HierarchyNode>();
 		level1Children.addAll(level1ChildSet);
@@ -175,7 +173,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 		
-		Set<HierarchyNode> level1ChildSet = actual.getChildren();
+		Collection<HierarchyNode> level1ChildSet = actual.getChildren();
 		assertEquals(2, level1ChildSet.size());
 		Vector<HierarchyNode> level1Children = new Vector<HierarchyNode>();
 		level1Children.addAll(level1ChildSet);
@@ -600,7 +598,7 @@
 		HierarchyNode root = tester.buildHierarchy(pages);
 		assertNotNull(root);
 		assertEquals(6, root.countDescendants());
-		Set<HierarchyNode> level1 = root.getChildren();
+		Collection<HierarchyNode> level1 = root.getChildren();
 		assertNotNull(level1);
 		assertEquals(2, level1.size());
 		for (HierarchyNode lvl1Node : level1) {
@@ -611,7 +609,7 @@
 			else if ("Drink.txt".equals(lvl1Node.getName())) fail("Still have Drink node");
 			else if ("Comestibles".equals(lvl1Node.getName())) {
 				assertEquals(3, lvl1Node.countDescendants());
-				Set<HierarchyNode> level2 = lvl1Node.getChildren();
+				Collection<HierarchyNode> level2 = lvl1Node.getChildren();
 				assertNotNull(level2);
 				assertEquals(2, level2.size());
 				for (HierarchyNode lvl2Node : level2) {
@@ -622,7 +620,7 @@
 			}
 			else if ("Liquid".equals(lvl1Node.getName())) {
 				assertEquals(2, lvl1Node.countDescendants());
-				Set<HierarchyNode> level2 = lvl1Node.getChildren();
+				Collection<HierarchyNode> level2 = lvl1Node.getChildren();
 				assertNotNull(level2);
 				assertEquals(1, level2.size());
 				for (HierarchyNode lvl2Node : level2) {
@@ -648,19 +646,19 @@
 		HierarchyNode root = tester.buildHierarchy(pages);
 		assertNotNull(root);
 		assertEquals(6, root.countDescendants());
-		Set<HierarchyNode> level1 = root.getChildren();
+		Collection<HierarchyNode> level1 = root.getChildren();
 		assertNotNull(level1);
 		assertEquals(1, level1.size());
 		for (HierarchyNode lvl1Node : level1) {
 			assertNotNull(lvl1Node);
 			if (lvl1Node.getName().endsWith("_subpages")) fail("Still have subpages dir node.");
 			assertEquals(5, lvl1Node.countDescendants());
-			Set<HierarchyNode> level2 = lvl1Node.getChildren();
+			Collection<HierarchyNode> level2 = lvl1Node.getChildren();
 			assertNotNull(level2);
 			assertEquals(1, level2.size());
 			for (HierarchyNode lvl2Node : level2) {
 					if (lvl2Node.getName().endsWith("_subpages")) fail("Still have subpages dir node.");
-					Set<HierarchyNode> level3 = lvl2Node.getChildren();
+					Collection<HierarchyNode> level3 = lvl2Node.getChildren();
 					assertNotNull(level3);
 					assertEquals(3, level3.size());
 			}
@@ -701,7 +699,7 @@
 		assertNull(actual.getParent());
 		assertNotNull(actual.getChildren());
 
-		Set<HierarchyNode> level1ChildSet = actual.getChildren();
+		Collection<HierarchyNode> level1ChildSet = actual.getChildren();
 		assertEquals(1, level1ChildSet.size());
 		assertEquals("sampleData.txt", level1ChildSet.iterator().next().getName());
 		
@@ -771,7 +769,7 @@
 		HierarchyNode rootnode = tester.buildHierarchy(pages);
 
 		assertNull(rootnode.getPage());
-		Set<HierarchyNode> actualrootnodeSet = rootnode.getChildren();
+		Collection<HierarchyNode> actualrootnodeSet = rootnode.getChildren();
 		HierarchyNode actualRootNode = null;
 		for (HierarchyNode next : actualrootnodeSet) {
 			actualRootNode = next;
@@ -781,7 +779,7 @@
 		assertNotNull(actualRoot);
 		assertEquals("Root", actualRoot.getName());
 		
-		Set<HierarchyNode> children = actualRootNode.getChildren();
+		Collection<HierarchyNode> children = actualRootNode.getChildren();
 		for (HierarchyNode childnode : children) {
 			Page childpage = childnode.getPage();
 			assertNotNull(childpage);
diff --git a/src/com/atlassian/uwc/hierarchies/HierarchyNode.java b/src/com/atlassian/uwc/hierarchies/HierarchyNode.java
index 9b04218..154b81a 100644
--- a/src/com/atlassian/uwc/hierarchies/HierarchyNode.java
+++ b/src/com/atlassian/uwc/hierarchies/HierarchyNode.java
@@ -1,13 +1,18 @@
 package com.atlassian.uwc.hierarchies;

 

+import java.util.Collection;

 import java.util.Comparator;

 import java.util.HashSet;

 import java.util.Iterator;

+import java.util.List;

 import java.util.Set;

 import java.util.TreeSet;

+import java.util.Vector;

 import java.util.regex.Matcher;

 import java.util.regex.Pattern;

 

+import org.apache.log4j.Logger;

+

 import com.atlassian.uwc.ui.Page;

 

 /**

@@ -19,9 +24,12 @@
     private String name;

     private Page page;

     private HierarchyNode parent;

-    private Set<HierarchyNode> children;

+    private Collection<HierarchyNode> children;

     private static Comparator childrenComparator;

+    private static boolean childrenAsList = false;

 

+    Logger log = Logger.getLogger(this.getClass());

+    

     public HierarchyNode() {

     }

 

@@ -62,19 +70,24 @@
      *

      * @param child The child to be removed. Must not be <code>null</code>.

      */

-    public void removeChild(HierarchyNode child) {

+    public boolean removeChild(HierarchyNode child) {

         if (child == null) {

             throw new IllegalArgumentException("The parameter must not be null!");

         }

-        if (children != null && children.contains(child)) {

-            children.remove(child);

+        if (getChildren().remove(child)) {

+            log.debug("Removed: '" + child.getName() + "' from " + this.getName());

             if (children.size() == 0) {

                 children = null;

             }

             child.setParent(null);

+            return true;

+        }

+        else {

+        	log.debug("Unable to remove this child: " + child.getName());

+        	return false;

         }

     }

-

+    

     /**

      * Returns the parent of this node.

      * @return The parent of this node, or <code>null</code> if

@@ -93,9 +106,12 @@
      * @return A set of child nodes, or an empty set if

      *         this node has no children.

      */

-    public Set<HierarchyNode> getChildren() {

+    public Collection<HierarchyNode> getChildren() {

     	if (this.children == null) {

-    		if (childrenComparator == null)

+    		if (childrenAsList) {

+    			this.children = new Vector<HierarchyNode>();

+    		}

+    		else if (childrenComparator == null)

     			this.children = new HashSet<HierarchyNode>();

     		else //if you want to be able to control the child sort order

     			this.children = new TreeSet<HierarchyNode>(childrenComparator);

@@ -112,7 +128,7 @@
         return children == null ? null : children.iterator();

     }

 

-    public void setChildren(Set<HierarchyNode> children) {

+    public void setChildren(Collection<HierarchyNode> children) {

         this.children = children;

     }

     

@@ -217,4 +233,25 @@
         i++;

         return i;

     }

+    

+//    public String toString() {

+//    	return treeAsString(this);

+//    }

+    public String treeAsString(HierarchyNode node) {

+		return treeAsString(node.getChildren(), "");

+	}

+

+	private String treeAsString(Collection<HierarchyNode> children, String delim) {

+		String msg = "";

+		for (HierarchyNode child : children) {

+			String newdelim = delim + " .";

+			msg += newdelim + child.getName() + "\n";

+			msg += treeAsString(child.getChildren(), newdelim);

+		}

+		return msg;

+	}

+	

+	public static void childrenAsList(boolean aslist) {

+		childrenAsList = aslist;

+	}

 }

diff --git a/src/com/atlassian/uwc/hierarchies/SmfHierarchy.java b/src/com/atlassian/uwc/hierarchies/SmfHierarchy.java
index 14fb305..d6c0058 100644
--- a/src/com/atlassian/uwc/hierarchies/SmfHierarchy.java
+++ b/src/com/atlassian/uwc/hierarchies/SmfHierarchy.java
@@ -10,7 +10,6 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Properties;
-import java.util.Set;
 import java.util.Vector;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -215,7 +214,7 @@
 				Boolean.parseBoolean(this.getProperties().getProperty(PROPKEY_COMMENTS))) {
 			Vector<HierarchyNode> topicNodes = getTopicNodes(root);
 			for (HierarchyNode topic : topicNodes) {
-				Set<HierarchyNode> children = topic.getChildren();
+				Collection<HierarchyNode> children = topic.getChildren();
 				for (Iterator iter = children.iterator(); iter.hasNext();) {
 					HierarchyNode reply = (HierarchyNode) iter.next();
 					String comment = createComment(reply.getName());
diff --git a/src/com/atlassian/uwc/ui/ConverterEngine.java b/src/com/atlassian/uwc/ui/ConverterEngine.java
index 95bca26..ac7c1d6 100644
--- a/src/com/atlassian/uwc/ui/ConverterEngine.java
+++ b/src/com/atlassian/uwc/ui/ConverterEngine.java
@@ -67,7 +67,7 @@
  * possibly some other method in the future)

  */

 public class ConverterEngine implements FeedbackHandler {

-    

+

 

 	/* START CONSTANTS */

 	private static final int NUM_REQ_CONVERTERS = 2;

@@ -93,10 +93,10 @@
 	private static final String DEFAULT_ATTACHMENT_UPLOAD_COMMENT = "Added by UWC, the Universal Wiki Converter";

 	public static final String PROPKEY_ENGINE_SAVES_TO_DISK = "engine-saves-to-disk";	

 	private static final String PROPKEY_SPACEPERMS = "spaceperms";

-	

+

 	/* START FIELDS */

 	public boolean running = false; //Methods check this to see if the conversion needs to be cancelled

-	

+

 	/**

 	 * used to disable check for illegal names and links. 

 	 * We want to allow users to override this so they can handle it themselves

@@ -106,48 +106,48 @@
 	private boolean autoDetectSpacekeys = false; //default = false

 	private HashSet<String> attachedFiles;//attachmentids

 	private HashSet<String> attachedPaths;//attachment file paths

-	

-    Logger log = Logger.getLogger(this.getClass());

-    // this logger is used to write out totals for the UWC to a seperate file uwc-totals.log

-    Logger totalsFileLog = Logger.getLogger("totalsFileLog");

-    Logger attachmentLog = Logger.getLogger("attachmentsLog");

 

-    /**

-     * The string that directory separators (e.g., / on Unix and \ on Windows) are replaced

-     * with in page titles.

-     * This is used by DokuWikiLinkConverter too.

-     */

-    public static final String CONFLUENCE_SEPARATOR = " -- ";

+	Logger log = Logger.getLogger(this.getClass());

+	// this logger is used to write out totals for the UWC to a seperate file uwc-totals.log

+	Logger totalsFileLog = Logger.getLogger("totalsFileLog");

+	Logger attachmentLog = Logger.getLogger("attachmentsLog");

 

-    protected enum HierarchyHandler {

-    	DEFAULT, 			//no hierarchy handling

-    	HIERARCHY_BUILDER,	//hierarchyBuilder handles 

-    	PAGENAME_HIERARCHIES//hierarchy maintained in pagename

-    }

-    private HierarchyHandler hierarchyHandler = HierarchyHandler.DEFAULT; 

-    

-    /**

-     * The mapping from file name extension to mime type that is used when sending

-     * attachments to Confluence.

-     * NOTE: static so that other files can get access to this easily.

-     */

-    private static MimetypesFileTypeMap mimeTypes;

+	/**

+	 * The string that directory separators (e.g., / on Unix and \ on Windows) are replaced

+	 * with in page titles.

+	 * This is used by DokuWikiLinkConverter too.

+	 */

+	public static final String CONFLUENCE_SEPARATOR = " -- ";

 

-    /**

-     * This is the location of the mime type mapping file. For details on the file format,

-     * refer to the link below.

-     *

-     * @see javax.activation.MimetypesFileTypeMap

-     */

-    public final static String mimetypeFileLoc = "conf" + File.separator + "mime.types";

+	protected enum HierarchyHandler {

+		DEFAULT, 			//no hierarchy handling

+		HIERARCHY_BUILDER,	//hierarchyBuilder handles 

+		PAGENAME_HIERARCHIES//hierarchy maintained in pagename

+	}

+	private HierarchyHandler hierarchyHandler = HierarchyHandler.DEFAULT; 

 

-    /**

-     * This field is set if a hierarchy builder "converter" is used. The field controls the

-     * way in which pages are added/updated in Confluence. If hierarchyBuilder is <code>null</code>, all

-     * pages are added as top-level pages in the selected space. Otherwise, the hierarchy builder is

-     * called on to create a page hierarchy, and the engine will insert the pages correspondingly.

-     */

-    private HierarchyBuilder hierarchyBuilder = null;

+	/**

+	 * The mapping from file name extension to mime type that is used when sending

+	 * attachments to Confluence.

+	 * NOTE: static so that other files can get access to this easily.

+	 */

+	private static MimetypesFileTypeMap mimeTypes;

+

+	/**

+	 * This is the location of the mime type mapping file. For details on the file format,

+	 * refer to the link below.

+	 *

+	 * @see javax.activation.MimetypesFileTypeMap

+	 */

+	public final static String mimetypeFileLoc = "conf" + File.separator + "mime.types";

+

+	/**

+	 * This field is set if a hierarchy builder "converter" is used. The field controls the

+	 * way in which pages are added/updated in Confluence. If hierarchyBuilder is <code>null</code>, all

+	 * pages are added as top-level pages in the selected space. Otherwise, the hierarchy builder is

+	 * called on to create a page hierarchy, and the engine will insert the pages correspondingly.

+	 */

+	private HierarchyBuilder hierarchyBuilder = null;

 	private UWCUserSettings settings;

 	private State state;

 	private Properties miscProperties = new Properties(); //instantiate this here - UWC-293

@@ -165,72 +165,72 @@
 

 	private int newNodes;

 

-    HashMap<String, Converter> converterCacheMap = new HashMap<String, Converter>();

+	HashMap<String, Converter> converterCacheMap = new HashMap<String, Converter>();

 	private long startTotalConvertTime;

-	

+

 	//Error handlers

 	private ConverterErrors errors = new ConverterErrors();

 	private boolean hadConverterErrors;

 	private HashMap<String, String> homepages = new HashMap<String, String>();

 

 	/* START CONSTRUCTORS */

-    

-    /**

-     * This default constructor initializes the mime types.

-     */

-    public ConverterEngine() {

-        try {

-            mimeTypes = new MimetypesFileTypeMap(new FileInputStream(mimetypeFileLoc));

-        } catch (FileNotFoundException e) {

-            String note = "Couldn't load mime types!";

-            log.error(note, e);

-            this.errors.addError(Feedback.BAD_SETTINGS_FILE, note, false);

-        }

-        totalsFileLog.setAdditivity(false);

-    }

-    

-    /* START METHODS */

 

-    /**

-     * converts the files with the converterstrings, and hooks any feedback into the given ui

-     * @param inputPages pages from the filesystem to be converted

-     * @param converterStrings list of converters as strings which will be run on the pages

-     * @param sendToConfluence true if the pages should be uploaded to confluence

-     * @param wikitype The wiki type that's being converted into Confluence, ex: Mediawiki

-     */

-    public void convert(List<File> inputPages, List<String> converterStrings, UWCUserSettings settings) {

-    	//setup

-    	this.running = true;

+	/**

+	 * This default constructor initializes the mime types.

+	 */

+	public ConverterEngine() {

+		try {

+			mimeTypes = new MimetypesFileTypeMap(new FileInputStream(mimetypeFileLoc));

+		} catch (FileNotFoundException e) {

+			String note = "Couldn't load mime types!";

+			log.error(note, e);

+			this.errors.addError(Feedback.BAD_SETTINGS_FILE, note, false);

+		}

+		totalsFileLog.setAdditivity(false);

+	}

+

+	/* START METHODS */

+

+	/**

+	 * converts the files with the converterstrings, and hooks any feedback into the given ui

+	 * @param inputPages pages from the filesystem to be converted

+	 * @param converterStrings list of converters as strings which will be run on the pages

+	 * @param sendToConfluence true if the pages should be uploaded to confluence

+	 * @param wikitype The wiki type that's being converted into Confluence, ex: Mediawiki

+	 */

+	public void convert(List<File> inputPages, List<String> converterStrings, UWCUserSettings settings) {

+		//setup

+		this.running = true;

 		resetFeedback();

 		resetErrorHandlers();

 		resetHierarchy();

-    	

+

 		//settings

 		boolean sendToConfluence = Boolean.parseBoolean(settings.getSendToConfluence());

-    	this.settings = settings;

-    	if (!this.running) {

-    		this.feedback = Feedback.CANCELLED;

-    		return;

-    	}

-    	

-    	//convert

+		this.settings = settings;

+		if (!this.running) {

+			this.feedback = Feedback.CANCELLED;

+			return;

+		}

+

+		//convert

 		convert(inputPages, converterStrings, sendToConfluence, settings.getPattern());

-		

+

 		//cleanup

 		if (this.feedback == Feedback.NONE)

 			this.feedback = Feedback.OK;

 		this.running = false;

-    }

-    

-    /**

-     * cancels the conversion

-     */

-    public void cancel() {

+	}

+

+	/**

+	 * cancels the conversion

+	 */

+	public void cancel() {

 		String message = "Engine - Sending Cancel Signal";

-    	log.debug(message);

+		log.debug(message);

 		this.state.updateNote(message);

-    	this.running = false;

-    }

+		this.running = false;

+	}

 

 	/**

 	 * gets a new State object.

@@ -245,12 +245,12 @@
 		//inputPages includes directories which are counted as 1 object in the inputPages list, and

 		//converterStrings includes non-converter properties. So, for now we'll use a default value to set up

 		//the state.

-//    	int steps = 

-//    		getNumberOfSteps(

-//    				inputPages, 

-//    				converterStrings, 

-//    				Boolean.parseBoolean(settings.getSendToConfluence())

-//    				);

+		//    	int steps = 

+		//    		getNumberOfSteps(

+		//    				inputPages, 

+		//    				converterStrings, 

+		//    				Boolean.parseBoolean(settings.getSendToConfluence())

+		//    				);

 		return getState(settings);

 	}

 

@@ -296,7 +296,7 @@
 	private int getNumberOfSteps(int pages, int converters, boolean sendToConfluence) {

 		return getNumberOfSteps(pages, converters, converters, sendToConfluence);

 	}

-	

+

 	/**

 	 * Counts the number of steps needed to do an entire conversion from start to finish.

 	 * Used with progress monitor

@@ -323,16 +323,16 @@
 	private int getNumberOfSteps(int files, int pages, int properties, int converters, boolean sendToConfluence) {

 		int numReqConverters = isIllegalHandlingEnabled()?NUM_REQ_CONVERTERS:0;

 		int steps = 

-			properties + 					//1. initialize converters (handles both converter and non-converter properties)

-			files + 						//2. create page objects (uses the original list of chosen file objects)

-			(converters * pages) +			//3. convert the files (uses the number of page objects)

-			(numReqConverters) +			//4. create required converters (2, right now)

-			(numReqConverters * pages) + 	//5. convert with required converters (2, right now)

-			pages + 						//6. save the files

-			(sendToConfluence?pages:0);		//7. upload pages if sendToConfluence

+				properties + 					//1. initialize converters (handles both converter and non-converter properties)

+				files + 						//2. create page objects (uses the original list of chosen file objects)

+				(converters * pages) +			//3. convert the files (uses the number of page objects)

+				(numReqConverters) +			//4. create required converters (2, right now)

+				(numReqConverters * pages) + 	//5. convert with required converters (2, right now)

+				pages + 						//6. save the files

+				(sendToConfluence?pages:0);		//7. upload pages if sendToConfluence

 		return steps;

 	}

-	

+

 	/**

 	 * converts the given pages using the given converterStrings, and sends the pages

 	 * to Confluence, if sendToConfluence is true.

@@ -345,148 +345,182 @@
 	}

 

 	/**

-     * converts the given pages not filtered out with the given filterPattern

-     * using the given converterStrings, and sends the pages to Confluence, 

-     * if sendToConfluence is true

-     * @param pages

-     * @param converterStrings

-     * @param sendToConfluence 

-     * @param filterPattern ignores files with this filter pattern

-     */

-    public void convert(List<File> pages, List<String> converterStrings, boolean sendToConfluence, String filterPattern) {

-    	log.info("Starting conversion.");

-    	

-    	initConversion();

-    	

-    	//create converters

-    	ArrayList<Converter> converters = createConverters(converterStrings);

+	 * converts the given pages not filtered out with the given filterPattern

+	 * using the given converterStrings, and sends the pages to Confluence, 

+	 * if sendToConfluence is true

+	 * @param pages

+	 * @param converterStrings

+	 * @param sendToConfluence 

+	 * @param filterPattern ignores files with this filter pattern

+	 */

+	public void convert(List<File> pages, List<String> converterStrings, boolean sendToConfluence, String filterPattern) {

+		log.info("Starting conversion.");

 

-    	//create page objects - Recurse through directories, adding all files

-    	FileFilter filter = createFilter(filterPattern);

-    	List<Page> allPages = createPages(filter, pages);

-    	

-    	//fix progressbar max, which is dependent on the previous two lists

-    	int steps = getNumberOfSteps(pages.size(), allPages.size(), converterStrings.size(), converters.size(), sendToConfluence);

-    	this.state.updateMax(steps);

-    	

-    	

-    	//convert the files

-    	if (convertPages(allPages, converters)) {

-    		//in case converting the pages disqualified some pages, we need to break if there are no pages left

-    		if (allPages.size() < 1) {

-    			String message = "All pages submitted were disqualified for various reasons. Could not complete conversion.";

+		initConversion();

+

+		//create converters

+		ArrayList<Converter> converters = createConverters(converterStrings);

+

+		//create page objects - Recurse through directories, adding all files

+		FileFilter filter = createFilter(filterPattern);

+		List<Page> allPages = createPages(filter, pages);

+

+		//fix progressbar max, which is dependent on the previous two lists

+		int steps = getNumberOfSteps(pages.size(), allPages.size(), converterStrings.size(), converters.size(), sendToConfluence);

+		this.state.updateMax(steps);

+

+

+		//convert the files

+		if (convertPages(allPages, converters)) {

+			//in case converting the pages disqualified some pages, we need to break if there are no pages left

+			if (allPages.size() < 1) {

+				String message = "All pages submitted were disqualified for various reasons. Could not complete conversion.";

 				log.warn(message);

 				this.errors.addError(Feedback.CONVERTER_ERROR, message, true);

 				this.state.updateMax(this.state.getStep()); //complete progress bar, prematurely

 				return;

-    		}

-    		//in case converting the pages disqualified some pages, we need to recompute progressbarmax

-    		steps = getNumberOfSteps(pages.size(), allPages.size(), converterStrings.size(), converters.size(), sendToConfluence);

-    		if (steps != this.state.getMax()) this.state.updateMax(steps);

-    		

-    		// do final required conversions. This step is seperate, due to state saving issues

-    		convertWithRequiredConverters(allPages);

+			}

+			//in case converting the pages disqualified some pages, we need to recompute progressbarmax

+			steps = getNumberOfSteps(pages.size(), allPages.size(), converterStrings.size(), converters.size(), sendToConfluence);

+			if (steps != this.state.getMax()) this.state.updateMax(steps);

 

-        	//save pages if engine-saves-to-disk property is true. Useful for debugging.

-    		//We are making this opt-in because users that don't need it will get a speed boost with fewer disk calls

-    		if (Boolean.parseBoolean(this.miscProperties.getProperty(PROPKEY_ENGINE_SAVES_TO_DISK, "false")))

-    				savePages(allPages, filterPattern);

-    		else log.debug("Engine Saves To Disk setting turned off.");

-			

+			// do final required conversions. This step is seperate, due to state saving issues

+			convertWithRequiredConverters(allPages);

+

+			//save pages if engine-saves-to-disk property is true. Useful for debugging.

+			//We are making this opt-in because users that don't need it will get a speed boost with fewer disk calls

+			if (Boolean.parseBoolean(this.miscProperties.getProperty(PROPKEY_ENGINE_SAVES_TO_DISK, "false")))

+				savePages(allPages, filterPattern);

+			else log.debug("Engine Saves To Disk setting turned off.");

+

 			//handling page histories and not sorting on create

 			if (isHandlingPageHistories() && 

 					!(isPageHistorySortOnCreate())) {

 				allPages = sortByHistory(allPages);

 			}

-			

-	    	//upload pages, if the user approves

-			if (sendToConfluence && this.running) {

-				if (hierarchyHandler == HierarchyHandler.HIERARCHY_BUILDER && hierarchyBuilder != null) {

-					//tell the hierarchy builder about the page histories framework

-					//do this here so that we're sure the page histories properties are set

-					if (hierarchyBuilder.getProperties() != null) { 

-						hierarchyBuilder.getProperties().setProperty("switch."+NONCONVERTERTYPE_PAGEHISTORYPRESERVATION, isHandlingPageHistories()+"");

-						if (getPageHistorySuffix() != null)	

-							hierarchyBuilder.getProperties().setProperty("suffix."+NONCONVERTERTYPE_PAGEHISTORYPRESERVATION, getPageHistorySuffix());

+

+			if (hierarchyHandler == HierarchyHandler.HIERARCHY_BUILDER && hierarchyBuilder != null) {

+				//tell the hierarchy builder about the page histories framework

+				//do this here so that we're sure the page histories properties are set

+				if (hierarchyBuilder.getProperties() != null) { 

+					hierarchyBuilder.getProperties().setProperty("switch."+NONCONVERTERTYPE_PAGEHISTORYPRESERVATION, isHandlingPageHistories()+"");

+					if (getPageHistorySuffix() != null)	

+						hierarchyBuilder.getProperties().setProperty("suffix."+NONCONVERTERTYPE_PAGEHISTORYPRESERVATION, getPageHistorySuffix());

+				}

+				//tell the hierarchy some other information

+				if (hierarchyBuilder.getProperties() != null) {

+					hierarchyBuilder.getProperties().setProperty("spacekey", settings.getSpace());

+				}

+				//build the hierarchy

+				HierarchyNode root = hierarchyBuilder.buildHierarchy(allPages);

+				int currenttotal = root.countDescendants()-1; //-1 for the null root;

+				log.debug("number of nodes in the hierarchy = " + root.countDescendants());

+				//upload pages, if the user approves

+				if (sendToConfluence && this.running) { //check here so that hierarchy can impact collisions without upload

+					if (Boolean.parseBoolean(this.miscProperties.getProperty("onlyorphans", "false"))) {

+						log.debug("Orphan attachments only.");

+						noteAttachments(root);

+					} 

+					else {

+						writeHierarchy(root, currenttotal, settings.getSpace());

 					}

-					//tell the hierarchy some other information

-					if (hierarchyBuilder.getProperties() != null) {

-						hierarchyBuilder.getProperties().setProperty("spacekey", settings.getSpace());

-					}

-					//build the hierarchy

-					HierarchyNode root = hierarchyBuilder.buildHierarchy(allPages);

-					int currenttotal = allPages.size();

-					if (hierarchyBuilder.getProperties().containsKey("newpagecount"))

-						currenttotal = Integer.parseInt(hierarchyBuilder.getProperties().getProperty("newpagescount"));

-					writeHierarchy(root, currenttotal, settings.getSpace());

 					handleOrphanAttachments();

-				} else { //no hierarchy - just write the pages

+				}

+				else if (!sendToConfluence){

+					log.debug("Send To Confluence setting turned off. --> Not uploading pages.");

+				}

+			} else { //no hierarchy

+				if (sendToConfluence && this.running) {//check here so that hierarchy can impact collisions without upload

 					writePages(allPages, settings.getSpace());

 					handleOrphanAttachments();

 				}

-				//check for namespace collisions and emit errors if found

-				//(after hierarchy has had a chance to make changes)

-				listCollisions(allPages);

+				else if (!sendToConfluence){

+					log.debug("Send To Confluence setting turned off. --> Not uploading pages.");

+				}

+			}

 

+			//check for namespace collisions and emit errors if found

+			//(after hierarchy has had a chance to make changes)

+			listCollisions(allPages);

+			clearAttachedFileList();

+		}	

+		log.info("Conversion Complete");

+	}

+

+	private void noteAttachments(HierarchyNode root) {

+		if (root.getPage() != null) {

+			log.debug("ORPHANDEBUG node: " + root.getPage().getName());

+			for (Attachment attachment : root.getPage().getAllAttachmentData()) {

+				alreadyAttached(root.getPage(), attachment.getFile());

+				if (root.getPage().getAncestors() != null) {

+					for (VersionPage ancestor : root.getPage().getAncestors()) {

+						for (Attachment ancAtt : ancestor.getAllAttachmentData()) {

+							log.debug("VERSION ORPHANDEBUG: " + ancestor.getName() + ": " + ancAtt.getFile().getName());

+							alreadyAttached(ancestor, ancAtt.getFile());

+						}

+					}

+				}

 			}

-			else if (!sendToConfluence){

-				log.debug("Send To Confluence setting turned off. --> Not uploading pages.");

+		}

+		if (!root.getChildren().isEmpty()) {

+			for (HierarchyNode child : root.getChildren()) {

+				noteAttachments(child);

 			}

-    	}	

-    	log.info("Conversion Complete");

-    }

+		}

+

+	}

 

 	protected boolean isPageHistorySortOnCreate() {

 		return Boolean.parseBoolean(this.miscProperties.getProperty("page-history-sortoncreate", "true"));

 	}

 

-    /**

-     * handle any cleanup

-     */

-    protected void initConversion() {

+	/**

+	 * handle any cleanup

+	 */

+	protected void initConversion() {

 		this.miscProperties.clear();

 	}

 

 	/**

-     * Instantiate all the converterStrings

-     *

-     * @param converterStrings a list of converter strings of the form "key=value"

-     * @return a list of converters

-     */

-    public ArrayList<Converter> createConverters(List<String> converterStrings) {

-    	return createConverters(converterStrings, true);

-    }

-    public ArrayList<Converter> createConverters(List<String> converterStrings, boolean runningState) {

-    	String message = "Initializing Converters...";

+	 * Instantiate all the converterStrings

+	 *

+	 * @param converterStrings a list of converter strings of the form "key=value"

+	 * @return a list of converters

+	 */

+	public ArrayList<Converter> createConverters(List<String> converterStrings) {

+		return createConverters(converterStrings, true);

+	}

+	public ArrayList<Converter> createConverters(List<String> converterStrings, boolean runningState) {

+		String message = "Initializing Converters...";

 		if (runningState) this.state.updateNote(message);

 		log.info(message);

-		

-        new DefaultXmlEvents().clearAll(); 	//everytime this method is called, have a clean slate of events

-        

-        ArrayList<Converter> converters = new ArrayList<Converter>(); 

-        this.numNonConverterProperties = 0; 

-        for (String converterStr : converterStrings) {

-        	if (runningState) this.state.updateProgress();

-        	if (runningState && !this.running) {

-        		this.feedback = Feedback.CANCELLED;

-        		return null;

-        	}

-            Converter converter;

-            if (isNonConverterProperty(converterStr)) {

-            	this.numNonConverterProperties++;

-            	handleNonConverterProperty(converterStr);

-            	continue;

-            } 

-        	converter = getConverterFromString(converterStr);

-        	if (converter == null) {

-        		continue;

-        	}

-        	converters.add(converter);

-        }

-        if (runningState) addDefaultMiscProperties();

-        

-        return converters;

-    }

+

+		new DefaultXmlEvents().clearAll(); 	//everytime this method is called, have a clean slate of events

+

+		ArrayList<Converter> converters = new ArrayList<Converter>(); 

+		this.numNonConverterProperties = 0; 

+		for (String converterStr : converterStrings) {

+			if (runningState) this.state.updateProgress();

+			if (runningState && !this.running) {

+				this.feedback = Feedback.CANCELLED;

+				return null;

+			}

+			Converter converter;

+			if (isNonConverterProperty(converterStr)) {

+				this.numNonConverterProperties++;

+				handleNonConverterProperty(converterStr);

+				continue;

+			} 

+			converter = getConverterFromString(converterStr);

+			if (converter == null) {

+				continue;

+			}

+			converters.add(converter);

+		}

+		if (runningState) addDefaultMiscProperties();

+

+		return converters;

+	}

 

 	/**

 	 * coverts pages with required converters

@@ -504,33 +538,33 @@
 			converters = createOneConverter(illegallinksConvStr);

 			convertPages(pages, converters, "Checking for links to illegal pagenames.");

 		} 

-    }

-    

-    /**

-     * converts the list of pages with the given converter

-     * @param pages list of pages to be converted

-     * @param useUI set this to false if you do not want the associated GUI elements

-     * to be updated. This is useful for unit testing.

-     */

-    protected void convertWithRequiredConverters(List<Page> pages, boolean useUI) {

-    	//XXX used only by the junit tests

-    	

-    	//create pagename converter and convert with it

+	}

+

+	/**

+	 * converts the list of pages with the given converter

+	 * @param pages list of pages to be converted

+	 * @param useUI set this to false if you do not want the associated GUI elements

+	 * to be updated. This is useful for unit testing.

+	 */

+	protected void convertWithRequiredConverters(List<Page> pages, boolean useUI) {

+		//XXX used only by the junit tests

+

+		//create pagename converter and convert with it

 		String pagenameConvStr = REQUIRED_CONVERTER_ILLEGAL_NAMES;

 		ArrayList<Converter> converters = createOneConverter(pagenameConvStr);

-    	convertPages(pages, converters, "Checking for illegal pagenames.");

-    	

-    	//get the state hashtable

-    	IllegalPageNameConverter  pagenameConverter = (IllegalPageNameConverter) converters.remove(0);

-    	HashSet<String> illegalNames =  pagenameConverter.getIllegalPagenames();

-    	

-    	//create linkname converter and convert with it

+		convertPages(pages, converters, "Checking for illegal pagenames.");

+

+		//get the state hashtable

+		IllegalPageNameConverter  pagenameConverter = (IllegalPageNameConverter) converters.remove(0);

+		HashSet<String> illegalNames =  pagenameConverter.getIllegalPagenames();

+

+		//create linkname converter and convert with it

 		String illegallinksConvStr = REQUIRED_CONVERTER_ILLEGAL_LINKS;

-    	converters = createOneConverter(illegallinksConvStr);

-    	IllegalLinkNameConverter linknameConverter = (IllegalLinkNameConverter) converters.get(0);

-    	linknameConverter.setIllegalPagenames(illegalNames);

-    	convertPages(pages, converters, "Checking for links to illegal pagenames.");

-    }

+		converters = createOneConverter(illegallinksConvStr);

+		IllegalLinkNameConverter linknameConverter = (IllegalLinkNameConverter) converters.get(0);

+		linknameConverter.setIllegalPagenames(illegalNames);

+		convertPages(pages, converters, "Checking for links to illegal pagenames.");

+	}

 

 	/**

 	 * creates the arraylist of converters when only one converter is needed

@@ -544,267 +578,270 @@
 		ArrayList<Converter> converters = createConverters(converterStrings);

 		return converters;

 	}

-    

+

 	/**

-     * Instantiates a converter from a correctly formatted String.

-     * <p/>

-     * Note: This method is now only called once per converter -- first all converters

-     * are created, then all pages, then all converters are run on all pages.

-     *

-     * @param converterStr A string of the form "name.keyword=parameters". The

-     *  keyword is used to create the correct type of converter, and the parameters

-     *  are then passed to the converter. Finally, the "name.keyword" part is set as

-     *  the key in the converter, mainly for debugging purposes. 

-     * @return converter or null if no converter can be parsed/instantiated

-     */

-    public Converter getConverterFromString(String converterStr) {

-        Converter converter;

-        int equalLoc = converterStr.indexOf("=");

-        String key = converterStr.substring(0, equalLoc);

-        String value = converterStr.substring(equalLoc + 1);

-        try {

-            if (key.indexOf(CONVERTERTYPE_CLASS) >= 0) {

-                converter = getConverterClassFromCache(value);

-            } else if (key.indexOf(CONVERTERTYPE_PERL) >= 0) {

-                converter = PerlConverter.getPerlConverter(value);

-                converter.setValue(value);

-            } else if (key.indexOf(CONVERTERTYPE_JAVAREGEXTOKEN) >= 0) {

-                converter = JavaRegexAndTokenizerConverter.getConverter(value);

-                converter.setValue(value);

-            } else if (key.indexOf(CONVERTERTYPE_JAVAREGEX) >= 0) {

-                converter = JavaRegexConverter.getConverter(value);

-                converter.setValue(value);

-            } else if (key.indexOf(CONVERTERTYPE_TWIKICLEANER) >= 0) {

-                //converter = getConverterClassFromCache(value);

-                converter = TWikiRegexConverterCleanerWrapper.getTWikiRegexConverterCleanerWrapper(value);

-                converter.setValue(value);

-            } else { 

-                String note = "Converter ignored -- name pattern not recognized: " + key;

+	 * Instantiates a converter from a correctly formatted String.

+	 * <p/>

+	 * Note: This method is now only called once per converter -- first all converters

+	 * are created, then all pages, then all converters are run on all pages.

+	 *

+	 * @param converterStr A string of the form "name.keyword=parameters". The

+	 *  keyword is used to create the correct type of converter, and the parameters

+	 *  are then passed to the converter. Finally, the "name.keyword" part is set as

+	 *  the key in the converter, mainly for debugging purposes. 

+	 * @return converter or null if no converter can be parsed/instantiated

+	 */

+	public Converter getConverterFromString(String converterStr) {

+		Converter converter;

+		int equalLoc = converterStr.indexOf("=");

+		String key = converterStr.substring(0, equalLoc);

+		String value = converterStr.substring(equalLoc + 1);

+		try {

+			if (key.indexOf(CONVERTERTYPE_CLASS) >= 0) {

+				converter = getConverterClassFromCache(value);

+			} else if (key.indexOf(CONVERTERTYPE_PERL) >= 0) {

+				converter = PerlConverter.getPerlConverter(value);

+				converter.setValue(value);

+			} else if (key.indexOf(CONVERTERTYPE_JAVAREGEXTOKEN) >= 0) {

+				converter = JavaRegexAndTokenizerConverter.getConverter(value);

+				converter.setValue(value);

+			} else if (key.indexOf(CONVERTERTYPE_JAVAREGEX) >= 0) {

+				converter = JavaRegexConverter.getConverter(value);

+				converter.setValue(value);

+			} else if (key.indexOf(CONVERTERTYPE_TWIKICLEANER) >= 0) {

+				//converter = getConverterClassFromCache(value);

+				converter = TWikiRegexConverterCleanerWrapper.getTWikiRegexConverterCleanerWrapper(value);

+				converter.setValue(value);

+			} else { 

+				String note = "Converter ignored -- name pattern not recognized: " + key;

 				this.errors.addError(Feedback.BAD_PROPERTY, note, true);

 				log.error(note);

-                return null;

-            }

-            converter.setProperties(this.miscProperties);

-            if (converter instanceof RequiresEngineConverter) {

-            	((RequiresEngineConverter) converter).setEngine(this);

-            }

-        } catch (ClassNotFoundException e) {

-            this.errors.addError(Feedback.BAD_CONVERTER_CLASS, "Converter ignored -- the Java class " + value + " was not found", true);

-            return null;

-        } catch (IllegalAccessException e) {

-            this.errors.addError(Feedback.BAD_CONVERTER_CLASS, "Converter ignored -- there was a problem creating a converter object", true);

-            return null;

-        } catch (InstantiationException e) {

-            this.errors.addError(Feedback.BAD_CONVERTER_CLASS, "Converter ignored -- there was a problem creating the Java class " + value, true);

-            return null;

-        } catch (ClassCastException e) {

-            this.errors.addError(Feedback.BAD_CONVERTER_CLASS, "Converter ignored -- the Java class " + value +

-                    " must implement the " + Converter.class.getName() + " interface!", true);

-            return null;

-        }

-        converter.setKey(key);

-        return converter;

-    }

-

-    /**

-     * handles necessary state changes for expected properties 

-     * that were set in the converter properties file.

-     * expected nonconverter properties include hierarchy builder properties

-     * and page history preservation properties

-     * @param converterStr should be a line from the converter properties file

-     * Example:

-     * MyWiki.0001.someproperty.somepropertytype=setting

-     * <br/>

-     * where somepropertytype is an expected property type:

-     * <br/>

-     * NONCONVERTERTYPE_HIERARCHYBUILDER or NONCONVERTERTYPE_PAGEHISTORYPRESERVATION

-     * or NONCONVERTERTYPE_ILLEGALHANDLING

-     * or NONCONVERTERTYPE_AUTODETECTSPACEKEYS

-     * or NONCONVERTERTYPE_FILTERS

-     * or NONCONVERTERTYPE_MISCPROPERTIES

-     */

-    protected void handleNonConverterProperty(String converterStr) {

-    	int equalLoc = converterStr.indexOf("=");

-        String key = converterStr.substring(0, equalLoc);

-        String value = converterStr.substring(equalLoc + 1);

-        String parent = "";

-        try {

-	    	if (key.indexOf(NONCONVERTERTYPE_HIERARCHYBUILDER) >= 0) {

-	    		if (isHierarchySwitch(key))

-	    			setHierarchyHandler(value);

-	    		else {

-					parent = HierarchyBuilder.class.getName(); 

-		            Class c;

-						c = Class.forName(value);

-		            HierarchyBuilder hierarchy = (HierarchyBuilder) c.newInstance();

-		            hierarchyBuilder = hierarchy;

-		            this.hierarchyBuilder.setProperties(this.miscProperties);

-	    		}

-	    	}

-	    	else if (key.endsWith(NONCONVERTERTYPE_PAGEHISTORYPRESERVATION)) {

-	    		handlePageHistoryProperty(key, value);

-	    	}

-	    	else if (key.endsWith(NONCONVERTERTYPE_ILLEGALHANDLING)) {

-	    		handleIllegalHandling(key, value);

-	    	}

-	    	else if (key.endsWith(NONCONVERTERTYPE_AUTODETECTSPACEKEYS)) {

-	    		handleAutoDetectSpacekeys(key, value);

-	    	}

-	    	else if (key.endsWith(NONCONVERTERTYPE_MISCPROPERTIES)) {

-	    		handleMiscellaneousProperties(key, value);

-	    	}

-	    	else if (key.endsWith(NONCONVERTERTYPE_FILTERS)) {

-	    		parent = FileFilter.class.getName();

-	    		handleFilters(key, value);

-	    	}

-	    	else if (key.endsWith(NONCONVERTERTYPE_XMLEVENT)) {

-	    		handleXmlEvents(key, value);

-	    	}

-        } catch (ClassNotFoundException e) { 

-            String message = "Property ignored -- the Java class " + value + " was not found";

-            log.error(message);

-			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

-        } catch (IllegalAccessException e) {

-            String message = "Property ignored -- there was a problem creating the Java class: " + value +

-            	".\n" +

-            	"Note: A necessary method's permissions were too restrictive. Check the constructor. ";

-            log.error(message);

-			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

-        } catch (InstantiationException e) {

-            String message = "Property ignored -- there was a problem creating the Java class " + value +

-            	".\n" +

-            	"Note: The class cannot be instantiated as it is abstract or is an interface.";

-            log.error(message);

-			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

-        } catch (ClassCastException e) { 

-			String message = "Property ignored -- the Java class " + value +

-			                    " must implement the " + parent + " interface!";

-            log.error(message);

-			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

-        } catch (IllegalArgumentException e) {

-        	String message = "Property ignored -- property value was not in expected format.";

-        	log.error(message);

-        	this.errors.addError(Feedback.BAD_PROPERTY, message, true);

-        }

-    }

-    

-    /**

-     * at long last making some performance enhancements

-     * here we are creating an object cache which should help a bit

-     *

-     * @param key A string representing the converter (actually the part after the

-     *        equals sign of the converter string).

-     * @return

-     * @throws ClassNotFoundException

-     * @throws IllegalAccessException

-     * @throws InstantiationException

-     */

-    private Converter getConverterClassFromCache(String key) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

-        Converter converter = converterCacheMap.get(key);

-        if (converter == null) {

-            Class c = Class.forName(key);

-            converter = (Converter) c.newInstance();

-            converterCacheMap.put(key, converter);

-        }

-        return converter;

-    }

-

-    /**

-     * creates file filter.

-     * If we have no filter values, returns null.

-     * If we have at least one filter value, uses the FilterChain class

-     * to create FileFilter that will handle all of the filter requirements.

-     * There are two types of supported filters: Class filters, and endswith filters.

-     * Class filters are fully resolved class names for classes that implement FileFilter.

-     * Endswith filters are text strings that the end of the filename must conform to.

-     * If there are more than one filter invoked, the following will be used to resolve 

-     * which files to accept: Only pages that all class filters accept as long as 

-     * any endswith filter accepts as well will be included. (Class filters are ANDed. 

-     * Endswith filters are ORed.) Example: If you had two endswiths, and a class: ".txt",

-     * ".xml", and NoSvnFilter, then .txt and .xml files that the NoSvnFilter accepts 

-     * will be included.

-     * @return FileFilter or null

-     */

-    protected FileFilter createFilter(final String pattern) {

-    	Set<String> values = getFilterValues();

-		if (pattern != null && !"".equals(pattern)) 

-    		values.add(pattern);

-

-    	if (values.isEmpty()) return null;

-    	

-    	FilterChain chain = new FilterChain(values, this.miscProperties);

-    	return chain.getFilter();

-    }

-    

-    /**

-     * Creates PageForXmlRpcOld objects for all the files in inputPages.

-     *

-     * @param filter file filter to be used to filter input pages, or null, if no such filter should be used

-     * @param inputPages   A list of files and directories that Pages should be created for.

-     * @return A list of PageForXmlRpcOld objects for all files matching the pattern in the settings.

-     */

-    protected List<Page> createPages(FileFilter filter, List<File> inputPages) {

-       	String message = "Initializing Pages...";

-		this.state.updateNote(message);

-		log.info(message);

-    	

-        List<Page> allPages = new LinkedList<Page>();

-

-        for (File fileOrDir : inputPages) {

-        	this.state.updateProgress();

-            List<Page> pages = recurse(fileOrDir, filter);

-            setupPages(fileOrDir, pages);

-            allPages.addAll(pages);

-        }

-        

-        if (isHandlingPageHistories() && isPageHistorySortOnCreate()) {

-        	return sortByHistory(allPages);

-        }

-        return allPages;

-    }

+				return null;

+			}

+			converter.setProperties(this.miscProperties);

+			if (converter instanceof RequiresEngineConverter) {

+				((RequiresEngineConverter) converter).setEngine(this);

+			}

+		} catch (ClassNotFoundException e) {

+			this.errors.addError(Feedback.BAD_CONVERTER_CLASS, "Converter ignored -- the Java class " + value + " was not found", true);

+			return null;

+		} catch (IllegalAccessException e) {

+			this.errors.addError(Feedback.BAD_CONVERTER_CLASS, "Converter ignored -- there was a problem creating a converter object", true);

+			return null;

+		} catch (InstantiationException e) {

+			this.errors.addError(Feedback.BAD_CONVERTER_CLASS, "Converter ignored -- there was a problem creating the Java class " + value, true);

+			return null;

+		} catch (ClassCastException e) {

+			this.errors.addError(Feedback.BAD_CONVERTER_CLASS, "Converter ignored -- the Java class " + value +

+					" must implement the " + Converter.class.getName() + " interface!", true);

+			return null;

+		}

+		converter.setKey(key);

+		return converter;

+	}

 

 	/**

-     * Recurses through a directory structure and adds all files in it matching the filter.

-     * Called by createPages.

-     *

-     * @param fileOrDir A directory or file. Must not be <code>null</code>.

-     * @param filter    the filter to use when selecting files

-     * @return A list with PageForXmlRpcOld objects for all the matching files in the directory and its subdirectories

-     */

-    private List<Page> recurse(File fileOrDir, FileFilter filter) {

-        assert fileOrDir != null;

-        List<Page> result = new LinkedList<Page>();

-        if (fileOrDir.isFile()) {									//it's a file AND

-        	if (filter == null || filter.accept(fileOrDir)) {		//there's no filter OR the filter accepts the file

-        		PageSplitter splitter = getPageSplitter();

-        		if (splitter == null)

-        			result.add(new Page(fileOrDir));

-        		else 

-        			result.addAll(splitter.split(fileOrDir));

-        	}

-            else 

-            	log.debug("Filtering out filename: " + fileOrDir.getName());

-        } else if (fileOrDir.isDirectory()) {

-            File[] files = fileOrDir.listFiles(filter);

-            for (File file : files) {

-                result.addAll(recurse(file, filter));

-            }

-        }

-        else { //some other problem

-        	String message = "Could not find file: '" +

-			        			fileOrDir.getAbsolutePath() +

-			        			"'.\n" +

-			        			"Check existence and permissions.";

+	 * handles necessary state changes for expected properties 

+	 * that were set in the converter properties file.

+	 * expected nonconverter properties include hierarchy builder properties

+	 * and page history preservation properties

+	 * @param converterStr should be a line from the converter properties file

+	 * Example:

+	 * MyWiki.0001.someproperty.somepropertytype=setting

+	 * <br/>

+	 * where somepropertytype is an expected property type:

+	 * <br/>

+	 * NONCONVERTERTYPE_HIERARCHYBUILDER or NONCONVERTERTYPE_PAGEHISTORYPRESERVATION

+	 * or NONCONVERTERTYPE_ILLEGALHANDLING

+	 * or NONCONVERTERTYPE_AUTODETECTSPACEKEYS

+	 * or NONCONVERTERTYPE_FILTERS

+	 * or NONCONVERTERTYPE_MISCPROPERTIES

+	 */

+	protected void handleNonConverterProperty(String converterStr) {

+		int equalLoc = converterStr.indexOf("=");

+		String key = converterStr.substring(0, equalLoc);

+		String value = converterStr.substring(equalLoc + 1);

+		String parent = "";

+		try {

+			if (key.indexOf(NONCONVERTERTYPE_HIERARCHYBUILDER) >= 0) {

+				if (isHierarchySwitch(key))

+					setHierarchyHandler(value);

+				else {

+					parent = HierarchyBuilder.class.getName(); 

+					Class c;

+					c = Class.forName(value);

+					HierarchyBuilder hierarchy = (HierarchyBuilder) c.newInstance();

+					hierarchyBuilder = hierarchy;

+					this.hierarchyBuilder.setProperties(this.miscProperties);

+				}

+			}

+			else if (key.endsWith(NONCONVERTERTYPE_PAGEHISTORYPRESERVATION)) {

+				handlePageHistoryProperty(key, value);

+			}

+			else if (key.endsWith(NONCONVERTERTYPE_ILLEGALHANDLING)) {

+				handleIllegalHandling(key, value);

+			}

+			else if (key.endsWith(NONCONVERTERTYPE_AUTODETECTSPACEKEYS)) {

+				handleAutoDetectSpacekeys(key, value);

+			}

+			else if (key.endsWith(NONCONVERTERTYPE_MISCPROPERTIES)) {

+				handleMiscellaneousProperties(key, value);

+			}

+			else if (key.endsWith(NONCONVERTERTYPE_FILTERS)) {

+				parent = FileFilter.class.getName();

+				handleFilters(key, value);

+			}

+			else if (key.endsWith(NONCONVERTERTYPE_XMLEVENT)) {

+				handleXmlEvents(key, value);

+			}

+		} catch (ClassNotFoundException e) { 

+			String message = "Property ignored -- the Java class " + value + " was not found";

+			log.error(message);

+			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

+		} catch (IllegalAccessException e) {

+			String message = "Property ignored -- there was a problem creating the Java class: " + value +

+					".\n" +

+					"Note: A necessary method's permissions were too restrictive. Check the constructor. ";

+			log.error(message);

+			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

+		} catch (InstantiationException e) {

+			String message = "Property ignored -- there was a problem creating the Java class " + value +

+					".\n" +

+					"Note: The class cannot be instantiated as it is abstract or is an interface.";

+			log.error(message);

+			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

+		} catch (ClassCastException e) { 

+			String message = "Property ignored -- the Java class " + value +

+					" must implement the " + parent + " interface!";

+			log.error(message);

+			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

+		} catch (IllegalArgumentException e) {

+			String message = "Property ignored -- property value was not in expected format.";

+			log.error(message);

+			this.errors.addError(Feedback.BAD_PROPERTY, message, true);

+		}

+	}

+

+	/**

+	 * at long last making some performance enhancements

+	 * here we are creating an object cache which should help a bit

+	 *

+	 * @param key A string representing the converter (actually the part after the

+	 *        equals sign of the converter string).

+	 * @return

+	 * @throws ClassNotFoundException

+	 * @throws IllegalAccessException

+	 * @throws InstantiationException

+	 */

+	private Converter getConverterClassFromCache(String key) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

+		Converter converter = converterCacheMap.get(key);

+		if (converter == null) {

+			Class c = Class.forName(key);

+			converter = (Converter) c.newInstance();

+			converterCacheMap.put(key, converter);

+		}

+		return converter;

+	}

+

+	/**

+	 * creates file filter.

+	 * If we have no filter values, returns null.

+	 * If we have at least one filter value, uses the FilterChain class

+	 * to create FileFilter that will handle all of the filter requirements.

+	 * There are two types of supported filters: Class filters, and endswith filters.

+	 * Class filters are fully resolved class names for classes that implement FileFilter.

+	 * Endswith filters are text strings that the end of the filename must conform to.

+	 * If there are more than one filter invoked, the following will be used to resolve 

+	 * which files to accept: Only pages that all class filters accept as long as 

+	 * any endswith filter accepts as well will be included. (Class filters are ANDed. 

+	 * Endswith filters are ORed.) Example: If you had two endswiths, and a class: ".txt",

+	 * ".xml", and NoSvnFilter, then .txt and .xml files that the NoSvnFilter accepts 

+	 * will be included.

+	 * @return FileFilter or null

+	 */

+	protected FileFilter createFilter(final String pattern) {

+		Set<String> values = getFilterValues();

+		if (pattern != null && !"".equals(pattern)) 

+			values.add(pattern);

+

+		if (values.isEmpty()) return null;

+

+		FilterChain chain = new FilterChain(values, this.miscProperties);

+		return chain.getFilter();

+	}

+

+	/**

+	 * Creates PageForXmlRpcOld objects for all the files in inputPages.

+	 *

+	 * @param filter file filter to be used to filter input pages, or null, if no such filter should be used

+	 * @param inputPages   A list of files and directories that Pages should be created for.

+	 * @return A list of PageForXmlRpcOld objects for all files matching the pattern in the settings.

+	 */

+	protected List<Page> createPages(FileFilter filter, List<File> inputPages) {

+		String message = "Initializing Pages...";

+		this.state.updateNote(message);

+		log.info(message);

+

+		List<Page> allPages = new LinkedList<Page>();

+

+		for (File fileOrDir : inputPages) {

+			this.state.updateProgress();

+			List<Page> pages = recurse(fileOrDir, filter);

+			setupPages(fileOrDir, pages);

+			allPages.addAll(pages);

+		}

+

+		log.debug("Number of page inputs (all): " + allPages.size());

+		if (isHandlingPageHistories() && isPageHistorySortOnCreate()) {

+			List<Page> sorted = sortByHistory(allPages);

+			log.debug("Number of page inputs (sorted): " + sorted.size());

+			return sorted;

+		}

+		return allPages;

+	}

+

+	/**

+	 * Recurses through a directory structure and adds all files in it matching the filter.

+	 * Called by createPages.

+	 *

+	 * @param fileOrDir A directory or file. Must not be <code>null</code>.

+	 * @param filter    the filter to use when selecting files

+	 * @return A list with PageForXmlRpcOld objects for all the matching files in the directory and its subdirectories

+	 */

+	private List<Page> recurse(File fileOrDir, FileFilter filter) {

+		assert fileOrDir != null;

+		List<Page> result = new LinkedList<Page>();

+		if (fileOrDir.isFile()) {									//it's a file AND

+			if (filter == null || filter.accept(fileOrDir)) {		//there's no filter OR the filter accepts the file

+				PageSplitter splitter = getPageSplitter();

+				if (splitter == null)

+					result.add(new Page(fileOrDir));

+				else 

+					result.addAll(splitter.split(fileOrDir));

+			}

+			else 

+				log.debug("Filtering out filename: " + fileOrDir.getName());

+		} else if (fileOrDir.isDirectory()) {

+			File[] files = fileOrDir.listFiles(filter);

+			for (File file : files) {

+				result.addAll(recurse(file, filter));

+			}

+		}

+		else { //some other problem

+			String message = "Could not find file: '" +

+					fileOrDir.getAbsolutePath() +

+					"'.\n" +

+					"Check existence and permissions.";

 			log.warn(message);

 			this.errors.addError(Feedback.BAD_FILE, message, true);

-        }

-        return result;

-    }

+		}

+		return result;

+	}

 

-    private PageSplitter getPageSplitter() {

-    	String classname = this.miscProperties.getProperty("pagesplitter", null);

-    	if (classname == null) return null;

-    	Class c;

+	private PageSplitter getPageSplitter() {

+		String classname = this.miscProperties.getProperty("pagesplitter", null);

+		if (classname == null) return null;

+		Class c;

 		try {

 			c = Class.forName(classname);

 		} catch (ClassNotFoundException e) {

@@ -823,29 +860,30 @@
 	}

 

 	/**

-     * Set the names of the pages and performs any other setup needed. Called by recurse().

-     * If the user selected a directory and this file is inside it, the base directory's

-     * path is removed and the rest is used as the page name.

-     * <p/>

-     * Any directory separators are replaced with the constant CONFLUENCE_SEPARATOR.

-     *

-     * @param baseDir The directory that the top-level documents are in

-     * @param pages A list of pages to set up

-     */

-    protected void setupPages(File baseDir, List<Page> pages) {

-        String basepath = baseDir.getParentFile().getPath() + File.separator;

-        int baselength = basepath.length();

+	 * Set the names of the pages and performs any other setup needed. Called by recurse().

+	 * If the user selected a directory and this file is inside it, the base directory's

+	 * path is removed and the rest is used as the page name.

+	 * <p/>

+	 * Any directory separators are replaced with the constant CONFLUENCE_SEPARATOR.

+	 *

+	 * @param baseDir The directory that the top-level documents are in

+	 * @param pages A list of pages to set up

+	 */

+	protected void setupPages(File baseDir, List<Page> pages) {

+		String basepath = baseDir.getParentFile().getPath() + File.separator;

+		int baselength = basepath.length();

 

-        for (Page page : pages) {

-            String pagePath = page.getFile().getPath();

-            String pageName = getPagename(pagePath.substring(baselength));

-            //Strip the file name from the path.

-            String path = getPath(pagePath);

-            page.setPath(path);

-            page.setName(pageName);

-            if (isHandlingPageHistoriesFromFilename()) preserveHistory(page, pageName);

-        }

-    }

+		for (Page page : pages) {

+			String pagePath = page.getFile().getPath();

+			String pageName = getPagename(pagePath.substring(baselength));

+			log.debug("New page: " + pageName + " -> " + pagePath);

+			//Strip the file name from the path.

+			String path = getPath(pagePath);

+			page.setPath(path);

+			page.setName(pageName);

+			if (isHandlingPageHistoriesFromFilename()) preserveHistory(page, pageName);

+		}

+	}

 

 	/**

 	 * figures out path var for Page based on complete path to page's file

@@ -855,77 +893,77 @@
 	private String getPath(String pagePath) {

 		int fileNameStart = pagePath.lastIndexOf(File.separator);

 		if (fileNameStart >= 0) {

-		    pagePath = pagePath.substring(0, fileNameStart);

+			pagePath = pagePath.substring(0, fileNameStart);

 		} else {

-		    pagePath = "";

+			pagePath = "";

 		}

 		return pagePath;

 	}

 

-    /**

-     * uses the filename to set the version and name of the given page

-     * so that the history is preserved in the conversion. Note:

-     * uses the pageHistorySuffix which is set by the handlePageHistoryProperty 

-     * method

-     * @param page object that will be changed to reflect pagename and version of given filename 

-     * @param filename should use the pageHistorySuffix to indicate version and pagename:

-     * <br/>

-     * if pageHistorySuffix is -#.txt

-     * <br/>

-     * then filename should be something like: pagename-2.txt

-     * @return Page with changed name and version

-     * Will return passed page with no changes if:

-     * <ul>

-     * <li>suffix is null</li>

-     * <li> suffix has no numerical indicator (#)</li>

-     * </ul>

-     */

-    protected Page preserveHistory(Page page, String filename) {

-    	if (loadOnAncestors()) {

-    		addAncestors(page);

-    		if (!page.getAncestors().isEmpty()) {

-    			page.setVersion(page.getLatestVersion()+1);

-    			log.debug("Current page version: " + page.getVersion());

-    		}

-    		return page;

-    	}

-    	return identifyHistoryOnePage(page, filename);

+	/**

+	 * uses the filename to set the version and name of the given page

+	 * so that the history is preserved in the conversion. Note:

+	 * uses the pageHistorySuffix which is set by the handlePageHistoryProperty 

+	 * method

+	 * @param page object that will be changed to reflect pagename and version of given filename 

+	 * @param filename should use the pageHistorySuffix to indicate version and pagename:

+	 * <br/>

+	 * if pageHistorySuffix is -#.txt

+	 * <br/>

+	 * then filename should be something like: pagename-2.txt

+	 * @return Page with changed name and version

+	 * Will return passed page with no changes if:

+	 * <ul>

+	 * <li>suffix is null</li>

+	 * <li> suffix has no numerical indicator (#)</li>

+	 * </ul>

+	 */

+	protected Page preserveHistory(Page page, String filename) {

+		if (loadOnAncestors()) {

+			addAncestors(page);

+			if (!page.getAncestors().isEmpty()) {

+				page.setVersion(page.getLatestVersion()+1);

+				log.debug("Current page version: " + page.getVersion());

+			}

+			return page;

+		}

+		return identifyHistoryOnePage(page, filename);

 	}

 

 	public Page identifyHistoryOnePage(Page page, String filename) {

 		//get suffix

 		String suffix = getPageHistorySuffix(); 

-    	if (suffix == null) {

-    		log.error("Error attempting to preserve history: Page history suffix is Null.");

-    		return page;

-    	}

-    	//create regex for filename based on the suffix

-    	Matcher hashFinder = hashPattern.matcher(suffix);

-    	String suffixReplaceRegex = "";

-    	if (hashFinder.find()) {

-    		suffixReplaceRegex = hashFinder.replaceAll("(\\\\d+)");

-    		suffixReplaceRegex = "(.*)" + suffixReplaceRegex;

-    	} 

-    	else {

-    		log.error("Error attempting to preserve history: Suffix is invalid. Must contain '#'.");

-    		return page;

-    	}

-    	//get the version and name

-    	Pattern suffixReplacePattern = Pattern.compile(suffixReplaceRegex);

-    	Matcher suffixReplacer = suffixReplacePattern.matcher(filename);

-    	if (suffixReplacer.find()) {

-    		String pagename = suffixReplacer.group(1);

-    		String versionString = suffixReplacer.group(2);

-    		page.setName(pagename); //set name before version so latestversion data is properly set in Page

-    		if (Boolean.parseBoolean(this.miscProperties.getProperty("page-history-sortwithtimestamp", "false"))) 

-    			page.setTimestamp(new Date(Long.parseLong(versionString)*1000));

-    		else

-    			page.setVersion(Integer.parseInt(versionString));

-    	}

-    	return page;

+		if (suffix == null) {

+			log.error("Error attempting to preserve history: Page history suffix is Null.");

+			return page;

+		}

+		//create regex for filename based on the suffix

+		Matcher hashFinder = hashPattern.matcher(suffix);

+		String suffixReplaceRegex = "";

+		if (hashFinder.find()) {

+			suffixReplaceRegex = hashFinder.replaceAll("(\\\\d+)");

+			suffixReplaceRegex = "(.*)" + suffixReplaceRegex;

+		} 

+		else {

+			log.error("Error attempting to preserve history: Suffix is invalid. Must contain '#'.");

+			return page;

+		}

+		//get the version and name

+		Pattern suffixReplacePattern = Pattern.compile(suffixReplaceRegex);

+		Matcher suffixReplacer = suffixReplacePattern.matcher(filename);

+		if (suffixReplacer.find()) {

+			String pagename = suffixReplacer.group(1);

+			String versionString = suffixReplacer.group(2);

+			page.setName(pagename); //set name before version so latestversion data is properly set in Page

+			if (Boolean.parseBoolean(this.miscProperties.getProperty("page-history-sortwithtimestamp", "false"))) 

+				page.setTimestamp(new Date(Long.parseLong(versionString)*1000));

+			else

+				page.setVersion(Integer.parseInt(versionString));

+		}

+		return page;

 	}

-    

-    /* Page History Load on Ancestors methods - START */

+

+	/* Page History Load on Ancestors methods - START */

 	private boolean loadOnAncestors() {

 		return Boolean.parseBoolean(this.miscProperties.getProperty("page-history-load-as-ancestors", "false"));

 	}

@@ -967,9 +1005,21 @@
 			}

 			page.setSortWithTimestamp(true); //affects sorting of collections of pages (including hierarchies)

 		}

-		

+		if (this.miscProperties.containsKey("page-history-maxversions")) { //useful for debugging

+			String maxString = this.miscProperties.getProperty("page-history-maxversions", null);

+			int max = Integer.parseInt(maxString);

+			if (max <= page.getAncestors().size()) {

+				log.debug("number of ancestors: " + page.getAncestors().size());

+				int actmax = page.getAncestors().size();

+				for (int i = actmax-1; i >=max; i--) {

+					page.getAncestors().remove(i);

+				}

+				log.debug("after limiting, number of ancestors: " + page.getAncestors().size());

+			}

+		}

+

 	}

-	

+

 	protected String getPageRelativePath(Page page) {

 		String ignorable = this.miscProperties.getProperty("filepath-hierarchy-ignorable-ancestors", "");

 		String full = page.getPath();

@@ -977,7 +1027,7 @@
 		return full.replaceAll("\\Q"+ignorable + "\\E", "");

 	}

 	/* Page History Load on Ancestors methods - END */

-	

+

 

 	/**

 	 * gets the pagename given the pagepath

@@ -988,14 +1038,14 @@
 		String pageName = "";

 		if (hierarchyHandler == HierarchyHandler.DEFAULT ||

 				hierarchyHandler == HierarchyHandler.HIERARCHY_BUILDER) {

-		    pageName = pagePath.substring(pagePath.lastIndexOf(File.separator) + 1);

+			pageName = pagePath.substring(pagePath.lastIndexOf(File.separator) + 1);

 		} else if (hierarchyHandler == HierarchyHandler.PAGENAME_HIERARCHIES) {

 			String quotedSeparator = Pattern.quote(File.separator);

 			pageName = pagePath.replaceAll(quotedSeparator, CONFLUENCE_SEPARATOR);

 		}

 		return pageName;

 	}

-	

+

 	/**

 	 * converts the given pages with the given converts

 	 * @param pages

@@ -1005,7 +1055,7 @@
 	protected boolean convertPages(List pages, List<Converter> converters) {

 		return convertPages(pages, converters, "Converting pages...");

 	}

-	

+

 	/**

 	 * converts the given pages with the given converters

 	 * @param pages

@@ -1017,49 +1067,48 @@
 		boolean result = true;

 		this.state.updateNote(note);

 		log.info(note);

-		

+

 		this.startTotalConvertTime = (new Date()).getTime();

-		

 		//go through each page

 		for (Iterator<Page> iter = pages.iterator(); iter.hasNext();) {

 			if (!this.running) {

 				this.feedback = Feedback.CANCELLED;

 				return false;

 			}

-			

+

 			Page page = iter.next();

 

 			//some bookkeeping

 			long startTimeStamp = conversionBookkeepingNextPage(page);

 

-            //get the file's contents

-			

+			//get the file's contents

+

 			if (page.getOriginalText() == null || "".equals(page.getOriginalText())) {

-	            File file = getFileContents(page);

-	            if (file == null) {

-	            	iter.remove(); //get rid of this page from the iterator.

-	            	continue;

-	            }

+				File file = getFileContents(page);

+				if (file == null) {

+					iter.remove(); //get rid of this page from the iterator.

+					continue;

+				}

 			} //else we used a PageSplitter to set the original text, so we can go straight to conversion

-            

-            //convert the page

-            convertPage(converters, page);

-            //more bookkeeping

-            conversionBookkeepingEndThisPage(startTimeStamp);

-            if (!this.running) {

-        		this.feedback = Feedback.CANCELLED;

-        		return false;

-        	}

-            if (page.getAncestors() != null && !page.getAncestors().isEmpty()) {

-            	convertPages(page.getAncestors(), converters);

-            }

-        }

+

+			//convert the page

+			convertPage(converters, page);

+			//more bookkeeping

+			conversionBookkeepingEndThisPage(startTimeStamp);

+			if (!this.running) {

+				this.feedback = Feedback.CANCELLED;

+				return false;

+			}

+			if (page.getAncestors() != null && !page.getAncestors().isEmpty()) {

+				convertPages(page.getAncestors(), converters);

+			}

+		}

 		//still more bookkeeping

 		conversionBookkeepingEndAll(pages, converters);

 		return result;

-		

+

 	}

-	

+

 	/**

 	 * make some log entries about the time it took to convert a page

 	 * @param startTimeStamp

@@ -1082,11 +1131,11 @@
 		String baseMessage = "::: total time to convert files: "+ totalTimeToConvert+ " seconds.";

 		log.info(baseMessage);

 		String message = baseMessage +

-						"For " +

-						pages.size() +

-						" pages and using " +

-						converters.size() +

-						" converters.";

+				"For " +

+				pages.size() +

+				" pages and using " +

+				converters.size() +

+				" converters.";

 		totalsFileLog.info(message);

 	}

 

@@ -1119,25 +1168,25 @@
 				file = new File(path);

 			}

 			else {

-		        log.warn("No file was set for page " + page.getName() + ". Skipping page.");

-		        return null;

+				log.warn("No file was set for page " + page.getName() + ". Skipping page.");

+				return null;

 			}

 		}

 		else if (page.getOriginalText() == null){

-		    try {

-		    	String pageContents = "";

-		    	if (isGzip() && page instanceof VersionPage) {

-		    		pageContents = getGzipText(file);

-		    	}

-		    	else pageContents = getAsciiText(file);

-		        page.setOriginalText(pageContents);

-		    } catch (IOException e) {

-		        String message = "Could not read file " + file.getAbsolutePath() + ".\n" +

-							        			"Check existence and permissions.";

-		        log.error(message);

+			try {

+				String pageContents = "";

+				if (isGzip() && page instanceof VersionPage) {

+					pageContents = getGzipText(file);

+				}

+				else pageContents = getAsciiText(file);

+				page.setOriginalText(pageContents);

+			} catch (IOException e) {

+				String message = "Could not read file " + file.getAbsolutePath() + ".\n" +

+						"Check existence and permissions.";

+				log.error(message);

 				this.errors.addError(Feedback.BAD_FILE, message, true);

-		        return null;

-		    }

+				return null;

+			}

 

 			// Save the true source since the original will get modified in convert.

 			page.setUnchangedSource(page.getOriginalText());

@@ -1157,7 +1206,7 @@
 	}

 

 	public String getAsciiText(File file) throws IOException,

-			UnsupportedEncodingException {

+	UnsupportedEncodingException {

 		String pageContents;

 		if (changingEncoding()) {

 			String encoding = getEncoding();

@@ -1183,7 +1232,7 @@
 

 	private String getEncoding() {

 		if (this.miscProperties != null)

-			 return this.miscProperties.getProperty("encoding", "utf-8");

+			return this.miscProperties.getProperty("encoding", "utf-8");

 		return "utf-8";

 	}

 

@@ -1198,98 +1247,96 @@
 			page.setConvertedText(page.getOriginalText()); //in case empty converter list

 

 		for (Converter converter : converters) {

-		    try {

-		    	this.state.updateProgress();

-		    	if (this.settings != null) {

-		    		converter.setAttachmentDirectory(this.settings.getAttachmentDirectory());

-		    	}

-		    	else {

-		    		//for backwards compatibility with v2

-		    		ConfluenceSettingsForm confSettings = UWCForm2.getInstance().getConfluenceSettingsForm();

-		    		converter.setAttachmentDirectory(confSettings.getAttachmentDirectory());

-		    	}

-		        converter.convert(page);

-		        // Need to reset originalText here because each converted expects

-		        // to start with the result of previous conversions.

-		        page.setOriginalText(page.getConvertedText());

-		    } catch (Exception e) {

-		        String note = "Exception thrown by converter " + converter.getKey() +

-						                " on page " + page.getName() + ". Continuing with next converter.";

-		        log.error(note, e);

+			try {

+				this.state.updateProgress();

+				if (this.settings != null) {

+					converter.setAttachmentDirectory(this.settings.getAttachmentDirectory());

+				}

+				else {

+					//for backwards compatibility with v2

+					ConfluenceSettingsForm confSettings = UWCForm2.getInstance().getConfluenceSettingsForm();

+					converter.setAttachmentDirectory(confSettings.getAttachmentDirectory());

+				}

+				converter.convert(page);

+				// Need to reset originalText here because each converted expects

+				// to start with the result of previous conversions.

+				page.setOriginalText(page.getConvertedText());

+			} catch (Exception e) {

+				String note = "Exception thrown by converter " + converter.getKey() +

+						" on page " + page.getName() + ". Continuing with next converter.";

+				log.error(note, e);

 				this.errors.addError(Feedback.CONVERTER_ERROR, note, true);

-		    }

-		    if (converter.getErrors().hasErrors()) {

-		    	this.hadConverterErrors = true;

-		    	this.state.updateNote(converter.getErrors().getFeedbackWindowErrorMessages());

-		    }

+			}

+			if (converter.getErrors().hasErrors()) {

+				this.hadConverterErrors = true;

+				this.state.updateNote(converter.getErrors().getFeedbackWindowErrorMessages());

+			}

 		}

-		

+

 		return page;

 	}

 

-    /**

-     * Write pages to disk. They are saved to the directory output/output below the

-     * current working directory.

-     *

-     * @param pages The pages to save

-     * @param pattern This file name extension is appended

-     *        to each page name to create the file name.

-     */

-    private void savePages(List<Page> pages, String pattern) {

-    	String message = "Saving Pages to Filesystem";

+	/**

+	 * Write pages to disk. They are saved to the directory output/output below the

+	 * current working directory.

+	 *

+	 * @param pages The pages to save

+	 * @param pattern This file name extension is appended

+	 *        to each page name to create the file name.

+	 */

+	private void savePages(List<Page> pages, String pattern) {

+		String message = "Saving Pages to Filesystem";

 		this.state.updateNote(message);

 		log.info(message);

-        

-    	FileUtils.createOutputDirIfNeeded();

-        

-    	String outputDirName = UWCGuiModel.getOutputDir();

-        log.debug("Output Directory = " + outputDirName);

 

-        File outputDir = new File(outputDirName);

-        if (!outputDir.exists() && !outputDir.mkdir()) {

-            String dirfailMessage = "Directory creation failed for directory " + outputDirName;

-            log.error(Feedback.BAD_OUTPUT_DIR + ": " + dirfailMessage);

+		FileUtils.createOutputDirIfNeeded();

+

+		String outputDirName = UWCGuiModel.getOutputDir();

+		log.debug("Output Directory = " + outputDirName);

+

+		File outputDir = new File(outputDirName);

+		if (!outputDir.exists() && !outputDir.mkdir()) {

+			String dirfailMessage = "Directory creation failed for directory " + outputDirName;

+			log.error(Feedback.BAD_OUTPUT_DIR + ": " + dirfailMessage);

 			this.errors.addError(Feedback.BAD_OUTPUT_DIR, dirfailMessage, true);

-        }

-        

-        for (Page page : pages) {

-        	if (!this.running) {

-        		this.feedback = Feedback.CANCELLED;

-        		return;

-        	}

-        	this.state.updateProgress();

-            String outputFileLoc = outputDirName + File.separator + page.getName() + pattern;

-            FileUtils.writeFile(page.getConvertedText(), outputFileLoc);

-        }

-    }

-    

-    protected Vector listCollisions(List<Page> pages) {

-    	Vector<String> collisions = new Vector<String>();

-    	//check to see if "off" property is present

-    	if (this.miscProperties != null && 

-    			this.miscProperties.containsKey("list-collisions") &&

-    			!Boolean.parseBoolean(this.miscProperties.getProperty("list-collisions"))) {

-    		log.debug("Namespace Collisions Feature turned off.");

-    		return collisions;

-    	}

-    	//sort

-    	Vector<Page> sorted = new Vector<Page>();

-    	sorted.addAll(pages);

-    	AsciiVersionComparator version = new AsciiVersionComparator();

-    	Collections.sort(sorted, version);

-    	//look for collisions

-    	Page last = new Page(null);

-    	last.setName("");

-    	last.setPath("");

-    	for (int i = 1; i < sorted.size(); i++) {

+		}

+

+		for (Page page : pages) {

+			if (!this.running) {

+				this.feedback = Feedback.CANCELLED;

+				return;

+			}

+			this.state.updateProgress();

+			String outputFileLoc = outputDirName + File.separator + page.getName() + pattern;

+			FileUtils.writeFile(page.getConvertedText(), outputFileLoc);

+		}

+	}

+

+	protected Vector listCollisions(List<Page> pages) {

+		Vector<String> collisions = new Vector<String>();

+		//check to see if "off" property is present

+		if (this.miscProperties != null && 

+				this.miscProperties.containsKey("list-collisions") &&

+				!Boolean.parseBoolean(this.miscProperties.getProperty("list-collisions"))) {

+			log.debug("Namespace Collisions Feature turned off.");

+			return collisions;

+		}

+		//sort

+		Vector<Page> sorted = new Vector<Page>();

+		sorted.addAll(pages);

+		AsciiVersionComparator version = new AsciiVersionComparator();

+		Collections.sort(sorted, version);

+		//look for collisions

+		Page last = new Page(null);

+		last.setName("");

+		last.setPath("");

+		for (int i = 1; i < sorted.size(); i++) {

 			Page page1 = (Page) sorted.get(i-1);

 			Page page2 = (Page) sorted.get(i);

 			log.debug("Checking for collisions: " + page1.getName() + " and " + page2.getName());

 			String collision = "";

 			//if each page lower cased is the same

-			if (getCollisionComparisonString(page1).equals(getCollisionComparisonString(page2))

-					&& page1.getVersion() == page2.getVersion() //and same page history version

-				) {

+			if (colliding(page1, page2)) {

 				if (getCollisionComparisonString(page1).equals(getCollisionComparisonString(last))) { //already have one for this name

 					String latestPath = getPagePath(page2);

 					String current = collisions.remove(collisions.size()-1);

@@ -1297,7 +1344,7 @@
 				}

 				else { //starting here

 					collision = getPagePath(page1) + page1.getName() + ", " +

-								getPagePath(page2) + page2.getName();

+							getPagePath(page2) + page2.getName();

 				}

 				collisions.add(collision);

 				last = page1;

@@ -1308,8 +1355,29 @@
 				this.log.error(error);

 			}

 		}

-    	return collisions;

-    }

+		return collisions;

+	}

+

+	protected boolean colliding(Page page1, Page page2) {

+		boolean name = getCollisionComparisonString(page1).equals(getCollisionComparisonString(page2));

+		boolean version = page1.getVersion() == page2.getVersion();

+

+		boolean space = false;

+		if (page1.getSpacekey() != null) { 

+			space = page1.getSpacekey().equals(page2.getSpacekey());

+		}

+		else if (page2.getSpacekey() != null) {

+			space = page2.getSpacekey().equals(page1.getSpacekey());

+		}

+		else if (page1.getSpacekey() == null && page2.getSpacekey() == null) 

+			space = true;

+

+		boolean path = !getPagePath(page1).equals(getPagePath(page2));

+		return name

+				&& version //and same page history version

+				&& space// and same space

+				&& path; // but not the same path

+	}

 

 	/**

 	 * @param page

@@ -1331,68 +1399,77 @@
 	private String getPagePath(Page page) {

 		return (page.getPath().endsWith(File.separator)?

 				page.getPath():

-				page.getPath()+File.separator);

+					page.getPath()+File.separator);

 	}

-    

-    /**

-     * Writes the pages to Confluence. If the process takes more than three seconds,

-     * a progress monitor will be displayed so that the user can see that something is

-     * indeed happening.

-     *

-     * @param pages The pages to output.

-     * @param spacekey space to which the pages will be written

-     */

-    protected void writePages(List pages, String spacekey) {

-    	String note = "Uploading Pages to Confluence...";

-		this.state.updateNote(note);

-		log.info(note);

-		

-        int numUploaded = 0;

-        List<Page> casted = (List<Page>) pages;

-        // at last, write the pages to Confluence!

-        for (Page page : casted) {

-        	this.state.updateProgress();

-        	if (!this.running) {

-        		this.feedback = Feedback.CANCELLED;

-        		return;

-        	}

-            if (sendPage(page, null, this.settings) == null) continue;

-            numUploaded++;

-            if ((numUploaded % 10) == 0) {

-                String message = "Uploaded " + numUploaded + 

-                	" out of " + pages.size() + 

-                	" page"+ (numUploaded==1?"":"s") +

-                	".";

-                this.state.updateNote(message);

-                log.info(message);

-            }

-        }

-        

-        String message = "Uploaded " + numUploaded + 

-		        		" out of " + pages.size() + 

-		        		" page"+ (numUploaded==1?"":"s") +

-		        		".";

-        this.state.updateNote(message);

-        log.info(message);

 

-        

-        //attachedFiles is cleared so that if we do another conversion

-        //without closing the UWC, it won't think the attachment has already been

-        //attached

-        this.attachedFiles = null;

-        this.attachedPaths = null;

-    }

+	/**

+	 * Writes the pages to Confluence. If the process takes more than three seconds,

+	 * a progress monitor will be displayed so that the user can see that something is

+	 * indeed happening.

+	 *

+	 * @param pages The pages to output.

+	 * @param spacekey space to which the pages will be written

+	 */

+	protected void writePages(List pages, String spacekey) {

+		writePages(pages, spacekey, true);

+	}

+	protected void writePages(List pages, String spacekey, boolean logging) {

+		if (logging) {

+			String note = "Uploading Pages to Confluence...";

+			this.state.updateNote(note);

+			log.info(note);

+		}

 

-    Pattern uploadedPattern = Pattern.compile("Attachment Uploaded: (.*)");

+		int numUploaded = 0;

+		List<Page> casted = (List<Page>) pages;

+		// at last, write the pages to Confluence!

+		for (Page page : casted) {

+			this.state.updateProgress();

+			if (!this.running) {

+				this.feedback = Feedback.CANCELLED;

+				return;

+			}

+			if (sendPage(page, null, this.settings) == null) continue;

+			numUploaded++;

+			if (logging && (numUploaded % 10) == 0) {

+				String message = "Uploaded " + numUploaded + 

+						" out of " + pages.size() + 

+						" page"+ (numUploaded==1?"":"s") +

+						".";

+				this.state.updateNote(message);

+				log.info(message);

+			}

+		}

+

+		if (logging) {

+			String message = "Uploaded " + numUploaded + 

+					" out of " + pages.size() + 

+					" page"+ (numUploaded==1?"":"s") +

+					".";

+			this.state.updateNote(message);

+			log.info(message);

+		}

+	}

+

+	protected void clearAttachedFileList() {

+		//attachedFiles is cleared so that if we do another conversion

+		//without closing the UWC, it won't think the attachment has already been

+		//attached

+		log.debug("Clearing Attached Filelist for next run.");

+		this.attachedFiles = null;

+		this.attachedPaths = null;

+	}

+

+	Pattern uploadedPattern = Pattern.compile("Attachment Uploaded: (.*)");

 	public void handleOrphanAttachments() {

 		if (this.settings.getUploadOrphanAttachments().equalsIgnoreCase("true"))

-        {

+		{

 			if (this.miscProperties.containsKey("attachments-uploaded-file")) {

 				identifyPreviouslyAttachedPaths(this.miscProperties.getProperty("attachments-uploaded-file"));

 			}

-        	ArrayList <File> orphanAttachments=findOrphanAttachments(this.settings.getAttachmentDirectory());

-        	uploadOrphanAttachments(orphanAttachments);

-        }

+			ArrayList <File> orphanAttachments=findOrphanAttachments(this.settings.getAttachmentDirectory());

+			uploadOrphanAttachments(orphanAttachments);

+		}

 	}

 

 	protected void identifyPreviouslyAttachedPaths(String uploadedpath) {

@@ -1413,124 +1490,124 @@
 			}

 		}

 	}

-	

+

 	//for unit testing purposes

 	protected void addAttachedPath(String path) {

 		if (this.attachedPaths == null) this.attachedPaths = new HashSet<String>();

 		this.attachedPaths.add(path);

 	}

-	

+

 	//for unit testing purposes

 	protected void clearAttachedPath() {

 		this.attachedPaths = null;

 	}

 

-	    

-    /******

-     * to find all orphan attachment files

-     * @param dirName

-     * @return

-     */

-    protected ArrayList<File> findOrphanAttachments(String dirName)

-    {

-    	ArrayList<File> orphanAttachments=new ArrayList<File>();

-    	File directory=new File(dirName);

-    	for (File file : directory.listFiles()) {

-    		if (file.isDirectory())

-    			orphanAttachments.addAll(findOrphanAttachments(file.getAbsolutePath()));

-    		else if (file.isFile())

-    		{

-    			if (orphanAlreadyAttached(file))

-    				continue;  

-    			log.debug("Found orphan attachment: " + file.getAbsolutePath());

-    			orphanAttachments.add(file);

-    		}

-        }

-    	

-    	return orphanAttachments;

-    	

-    	

-    }

-    /**

-     * to upload the orphan attachment files to wiki.

-     */

-    protected void uploadOrphanAttachments(ArrayList<File> orphanAttachments)

-    {

-    	if (orphanAttachments == null || orphanAttachments.size() == 0)

-    		return;

-    	

-    	Hashtable pageTable = new Hashtable();

-    	pageTable.put("content", "");

-    	pageTable.put("title", ORPHAN_ATTACHMENTS_PAGE_TITLE); 

-    	

-    	//create ConfluenceServerSettings object

-    	ConfluenceServerSettings confSettings = new ConfluenceServerSettings();

-    	confSettings.login = settings.getLogin();

-    	confSettings.password = settings.getPassword();

-    	confSettings.url = settings.getUrl(); 

-    	confSettings.spaceKey = settings.getSpace();

-    	confSettings.truststore = settings.getTruststore();

-    	confSettings.trustpass = settings.getTrustpass();

-    	confSettings.trustallcerts = settings.getTrustall();

-    	

-    	RemoteWikiBroker broker = RemoteWikiBroker.getInstance();	

-     	//check for problems with settings 

-    	checkConfluenceSettings(confSettings);

-    	//send page

-    	String pageId = sendPage(broker, pageTable, confSettings);

-     	

-    	int total=orphanAttachments.size();

-    	int count=0;

-    	for (File file : orphanAttachments) {

-    		sendAttachment(file, broker, pageId, confSettings);

-    		count++;

-    		if ((count % 10) == 0) {

-                String message = "Uploaded " + count + 

-                	" out of " + total + " orphan attachments.";

-                this.state.updateNote(message);

-                log.info(message);

-            }

-        }

-    	

-    	String message = "Uploaded " + total + " orphan attachments.";

+

+	/******

+	 * to find all orphan attachment files

+	 * @param dirName

+	 * @return

+	 */

+	protected ArrayList<File> findOrphanAttachments(String dirName)

+	{

+		ArrayList<File> orphanAttachments=new ArrayList<File>();

+		File directory=new File(dirName);

+		for (File file : directory.listFiles()) {

+			if (file.isDirectory())

+				orphanAttachments.addAll(findOrphanAttachments(file.getAbsolutePath()));

+			else if (file.isFile())

+			{

+				if (orphanAlreadyAttached(file))

+					continue;  

+				log.debug("Found orphan attachment: " + file.getAbsolutePath());

+				orphanAttachments.add(file);

+			}

+		}

+

+		return orphanAttachments;

+

+

+	}

+	/**

+	 * to upload the orphan attachment files to wiki.

+	 */

+	protected void uploadOrphanAttachments(ArrayList<File> orphanAttachments)

+	{

+		if (orphanAttachments == null || orphanAttachments.size() == 0)

+			return;

+

+		Hashtable pageTable = new Hashtable();

+		pageTable.put("content", "");

+		pageTable.put("title", ORPHAN_ATTACHMENTS_PAGE_TITLE); 

+

+		//create ConfluenceServerSettings object

+		ConfluenceServerSettings confSettings = new ConfluenceServerSettings();

+		confSettings.login = settings.getLogin();

+		confSettings.password = settings.getPassword();

+		confSettings.url = settings.getUrl(); 

+		confSettings.spaceKey = settings.getSpace();

+		confSettings.truststore = settings.getTruststore();

+		confSettings.trustpass = settings.getTrustpass();

+		confSettings.trustallcerts = settings.getTrustall();

+

+		RemoteWikiBroker broker = RemoteWikiBroker.getInstance();	

+		//check for problems with settings 

+		checkConfluenceSettings(confSettings);

+		//send page

+		String pageId = sendPage(broker, pageTable, confSettings);

+

+		int total=orphanAttachments.size();

+		int count=0;

+		for (File file : orphanAttachments) {

+			sendAttachment(file, broker, pageId, confSettings);

+			count++;

+			if ((count % 10) == 0) {

+				String message = "Uploaded " + count + 

+						" out of " + total + " orphan attachments.";

+				this.state.updateNote(message);

+				log.info(message);

+			}

+		}

+

+		String message = "Uploaded " + total + " orphan attachments.";

 		this.state.updateNote(message);

 		log.info(message);

-    	  	

-    }

-    

-    /****

-     * send an attachment file to wiki.

-     * 

-     * @param file

-     * @param broker

-     * @param pageId

-     * @param confSettings

-     * @return the attachment object we sent. used by junit tests

-     */

-    protected AttachmentForXmlRpc sendAttachment(File file, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings) {

-    	return sendAttachment(new Attachment(file), broker, pageId, confSettings);

-    }

-    protected AttachmentForXmlRpc sendAttachment(Attachment attachment, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings)

-    {

-    	File file = attachment.getFile();

-    	AttachmentForXmlRpc attachmentRpc = new AttachmentForXmlRpc();

-        if (tooBig(file) || doesNotExist(file)) 

-        	return null;

-        attachmentRpc.setFileName(attachment.getName()); 

-        attachmentRpc.setFileLocation(file.getAbsolutePath());

-        attachmentRpc.setContentType(determineContentType(file)); //XXX Note: if the filename is different from the file, the content type determining might be foiled

-        attachmentRpc.setComment(attachment.getComment() == null?getAttachmentUploadComment():attachment.getComment());

-        String errorMessage = "Couldn't send attachmentRpc " +

-        		file.getAbsolutePath() + ". Skipping attachmentRpc.";

-        if (usingWebdav()) {

-        	String webdavPath = getWebdavPath();

-        	sendAttachmentWebdav(broker, pageId, confSettings, attachmentRpc, webdavPath, errorMessage);

-        }

-        else 

-        	sendAttachmentRemoteAPI(broker, pageId, confSettings, attachmentRpc, errorMessage);

-        attachmentLog.info("Attachment Uploaded: " + file.getAbsolutePath());

-        return attachmentRpc;//for junit tests

-    }

+

+	}

+

+	/****

+	 * send an attachment file to wiki.

+	 * 

+	 * @param file

+	 * @param broker

+	 * @param pageId

+	 * @param confSettings

+	 * @return the attachment object we sent. used by junit tests

+	 */

+	protected AttachmentForXmlRpc sendAttachment(File file, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings) {

+		return sendAttachment(new Attachment(file), broker, pageId, confSettings);

+	}

+	protected AttachmentForXmlRpc sendAttachment(Attachment attachment, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings)

+	{

+		File file = attachment.getFile();

+		AttachmentForXmlRpc attachmentRpc = new AttachmentForXmlRpc();

+		if (tooBig(file) || doesNotExist(file)) 

+			return null;

+		attachmentRpc.setFileName(attachment.getName()); 

+		attachmentRpc.setFileLocation(file.getAbsolutePath());

+		attachmentRpc.setContentType(determineContentType(file)); //XXX Note: if the filename is different from the file, the content type determining might be foiled

+		attachmentRpc.setComment(attachment.getComment() == null?getAttachmentUploadComment():attachment.getComment());

+		String errorMessage = "Couldn't send attachmentRpc " +

+				file.getAbsolutePath() + ". Skipping attachmentRpc.";

+		if (usingWebdav()) {

+			String webdavPath = getWebdavPath();

+			sendAttachmentWebdav(broker, pageId, confSettings, attachmentRpc, webdavPath, errorMessage);

+		}

+		else 

+			sendAttachmentRemoteAPI(broker, pageId, confSettings, attachmentRpc, errorMessage);

+		attachmentLog.info("Attachment Uploaded: " + file.getAbsolutePath());

+		return attachmentRpc;//for junit tests

+	}

 

 	private String getAttachmentUploadComment() {

 		if (this.miscProperties != null &&

@@ -1560,144 +1637,144 @@
 	}

 

 	private void sendAttachmentWebdav(RemoteWikiBroker broker, String pageId, 

-										ConfluenceServerSettings confSettings, 

-										AttachmentForXmlRpc attachment, String basepath, 

-										String errorMessage) {

+			ConfluenceServerSettings confSettings, 

+			AttachmentForXmlRpc attachment, String basepath, 

+			String errorMessage) {

 		try {

 			Map pagesByIdMap = broker.getAllServerPagesMapById(confSettings, confSettings.spaceKey);

 			String webdavPath = broker.getWebDAVPagePath(confSettings.url, confSettings.spaceKey, pageId, pagesByIdMap, basepath);

 			broker.sendFileViaWebDAV(attachment.getFileLocation(), webdavPath, confSettings.login, confSettings.password);

 		} catch (IOException e) {

-        	log.error(Feedback.BAD_FILE + ": " + errorMessage, e);

-            this.errors.addError(Feedback.BAD_FILE, errorMessage, true);

-        } catch (XmlRpcException e) {

-        	log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage, e);

-            this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

-            if (Pattern.matches(".*?You do not have the permissions.*", e.getMessage())) {

-            	String noPermissionsError = "User '" + confSettings.login + 

-            		"' " +

-            		"does not have permission to attach files to space '" + 

-            		confSettings.spaceKey +

-            		"'.";

-            	log.debug(Feedback.USER_NOT_PERMITTED + ": " + noPermissionsError);

-            	this.errors.addError(Feedback.USER_NOT_PERMITTED, noPermissionsError, true);

-            }

-        }

-		

+			log.error(Feedback.BAD_FILE + ": " + errorMessage, e);

+			this.errors.addError(Feedback.BAD_FILE, errorMessage, true);

+		} catch (XmlRpcException e) {

+			log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage, e);

+			this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

+			if (Pattern.matches(".*?You do not have the permissions.*", e.getMessage())) {

+				String noPermissionsError = "User '" + confSettings.login + 

+						"' " +

+						"does not have permission to attach files to space '" + 

+						confSettings.spaceKey +

+						"'.";

+				log.debug(Feedback.USER_NOT_PERMITTED + ": " + noPermissionsError);

+				this.errors.addError(Feedback.USER_NOT_PERMITTED, noPermissionsError, true);

+			}

+		}

+

 	}

 

 	private void sendAttachmentRemoteAPI(RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings, AttachmentForXmlRpc attachment, String errorMessage) {

 		try {

-            broker.storeAttachment(confSettings, pageId, attachment);

-        } catch (IOException e) {

-        	log.error(Feedback.BAD_FILE + ": " + errorMessage, e);

-            this.errors.addError(Feedback.BAD_FILE, errorMessage, true);

-        } catch (XmlRpcException e) {

-        	log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage, e);

-            this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

-            if (Pattern.matches(".*?You do not have the permissions.*", e.getMessage())) {

-            	String noPermissionsError = "User '" + confSettings.login + 

-            		"' " +

-            		"does not have permission to attach files to space '" + 

-            		confSettings.spaceKey +

-            		"'.";

-            	log.debug(Feedback.USER_NOT_PERMITTED + ": " + noPermissionsError);

-            	this.errors.addError(Feedback.USER_NOT_PERMITTED, noPermissionsError, true);

-            }

-        }

+			broker.storeAttachment(confSettings, pageId, attachment);

+		} catch (IOException e) {

+			log.error(Feedback.BAD_FILE + ": " + errorMessage, e);

+			this.errors.addError(Feedback.BAD_FILE, errorMessage, true);

+		} catch (XmlRpcException e) {

+			log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage, e);

+			this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

+			if (Pattern.matches(".*?You do not have the permissions.*", e.getMessage())) {

+				String noPermissionsError = "User '" + confSettings.login + 

+						"' " +

+						"does not have permission to attach files to space '" + 

+						confSettings.spaceKey +

+						"'.";

+				log.debug(Feedback.USER_NOT_PERMITTED + ": " + noPermissionsError);

+				this.errors.addError(Feedback.USER_NOT_PERMITTED, noPermissionsError, true);

+			}

+		}

 	}

-    /**

-     * Writes a hierarchy of pages to Confluence. The empty nodes (those with page=null) will

-     * not be written if there already exists a page in Confluence. If there is no page at the

-     * corresponding place in Confluence, an empty page will be created.

-     *

-     * Like writePages(), this method will show a progress bar if the hierarchy takes more than

-     * a few seconds to send to Confluence.

-     *

-     * @param root The root of the hierarchy. Note: The root node itself will <strong>NOT</strong> be

-     *        added to Confluence. All it's children will be added as top-level pages in the space.

-     * @param maxProgress used by logging and status messages

-     * @param spacekey space to which the pages will be written

-     */

-    protected void writeHierarchy(HierarchyNode root, int maxProgress, String spacekey) {

-    	String message = "Uploading Pages to Confluence...";

+	/**

+	 * Writes a hierarchy of pages to Confluence. The empty nodes (those with page=null) will

+	 * not be written if there already exists a page in Confluence. If there is no page at the

+	 * corresponding place in Confluence, an empty page will be created.

+	 *

+	 * Like writePages(), this method will show a progress bar if the hierarchy takes more than

+	 * a few seconds to send to Confluence.

+	 *

+	 * @param root The root of the hierarchy. Note: The root node itself will <strong>NOT</strong> be

+	 *        added to Confluence. All it's children will be added as top-level pages in the space.

+	 * @param maxProgress used by logging and status messages

+	 * @param spacekey space to which the pages will be written

+	 */

+	protected void writeHierarchy(HierarchyNode root, int maxProgress, String spacekey) {

+		String message = "Uploading Pages to Confluence...";

 		this.state.updateNote(message);

 		log.info(message);

-		

-        int progressNum = 0;

-        this.newNodes = 0; //this has to be a field, because we're already returning something; 

-        // at last write the pages to Confluence!

-        for (HierarchyNode topLevelPage : root.getChildren()) {

-			   log.debug("writeHierarchy: toplevelpage = " + topLevelPage.getName());

-				log.debug("number of children this toplevelpage has = " + topLevelPage.getChildren().size());

-            progressNum = writeHierarchy(topLevelPage, null, progressNum, maxProgress, spacekey);

-            if (!this.running) {

-        		this.feedback = Feedback.CANCELLED;

-        		return;

-        	}

-        }

-    }

 

-    /**

-     * This is the recursive part of <code>writeHierarchy</code>. Don't call this directly!

-     * Call writeHierarchy(root) instead.

-     *

-     * @param node The current node in the hierarchy

-     * @param parentId The Confluence "page ID" of the parent page

-     * @param progress The number of pages that have been converted so far

-     *        (used to keep the progress monitor updated)

-     * @param spacekey space to which the pages will be written

-     * @return The number of pages converted after this node and all its descendants have been added.

-     */

-    private int writeHierarchy(

-    		HierarchyNode node, 

-    		String parentId, 

-    		int progress, 

-    		int maxProgress, 

-    		String spacekey) {

-    	if (!this.running) {

-    		this.feedback = Feedback.CANCELLED;

-    		return progress;

-    	}

-    	// First upload the page contained in this node

-        Page page = node.getPage();

-        // create missing nodes - like a directory that didn't have a corresponding page

-        if (page == null) {

-            // This node is a "placeholder" because there are pages further down the hierarchy but

-            // for some reason this node was not included in the conversion. Create an empty page.

-            // Note that this page will only be sent to Confluence if there was no page in place before.

-            page = new Page(null);

-            page.setName(node.getName());

-            page.setOriginalText("");

-            page.setConvertedText("");

-            page.setPath(node.getName()); //needed by auto-detect spacekeys feature

+		int progressNum = 0;

+		this.newNodes = 0; //this has to be a field, because we're already returning something; 

+		// at last write the pages to Confluence!

+		for (HierarchyNode topLevelPage : root.getChildren()) {

+			log.debug("writeHierarchy: toplevelpage = " + topLevelPage.getName());

+			log.debug("number of children this toplevelpage has = " + topLevelPage.getChildren().size());

+			progressNum = writeHierarchy(topLevelPage, null, progressNum, maxProgress, spacekey);

+			if (!this.running) {

+				this.feedback = Feedback.CANCELLED;

+				return;

+			}

+		}

+	}

 

-            String message = "Page '" + page.getName() + "' does not exist. Creating it now.";

+	/**

+	 * This is the recursive part of <code>writeHierarchy</code>. Don't call this directly!

+	 * Call writeHierarchy(root) instead.

+	 *

+	 * @param node The current node in the hierarchy

+	 * @param parentId The Confluence "page ID" of the parent page

+	 * @param progress The number of pages that have been converted so far

+	 *        (used to keep the progress monitor updated)

+	 * @param spacekey space to which the pages will be written

+	 * @return The number of pages converted after this node and all its descendants have been added.

+	 */

+	private int writeHierarchy(

+			HierarchyNode node, 

+			String parentId, 

+			int progress, 

+			int maxProgress, 

+			String spacekey) {

+		if (!this.running) {

+			this.feedback = Feedback.CANCELLED;

+			return progress;

+		}

+		// First upload the page contained in this node

+		Page page = node.getPage();

+		// create missing nodes - like a directory that didn't have a corresponding page

+		if (page == null) {

+			// This node is a "placeholder" because there are pages further down the hierarchy but

+			// for some reason this node was not included in the conversion. Create an empty page.

+			// Note that this page will only be sent to Confluence if there was no page in place before.

+			page = new Page(null);

+			page.setName(node.getName());

+			page.setOriginalText("");

+			page.setConvertedText("");

+			page.setPath(node.getName()); //needed by auto-detect spacekeys feature

+

+			String message = "Page '" + page.getName() + "' does not exist. Creating it now.";

 			log.info(message);

 			this.state.updateNote(message);

-            

-            this.newNodes++;

-            this.state.updateMax(this.state.getMax() + 1);

-        }

-        

-        //upload the page

-        String myId = sendPage(page, parentId, this.settings);

-        

-        //some bookkeeping

-        progress++;

-        logProgressMessage(progress, maxProgress);

-        

-        // Then recursively upload all the node's descendants

-        for (HierarchyNode child : node.getChildren()) {

-            progress = writeHierarchy(child, myId, progress, maxProgress, spacekey);

-            if (!this.running) {

-        		this.feedback = Feedback.CANCELLED;

-        		return progress;

-        	}

-        }

-        

-        return progress;

-    }

+

+			this.newNodes++;

+			this.state.updateMax(this.state.getMax() + 1);

+		}

+

+		//upload the page

+		String myId = sendPage(page, parentId, this.settings);

+

+		//some bookkeeping

+		progress++;

+		logProgressMessage(progress, maxProgress);

+

+		// Then recursively upload all the node's descendants

+		for (HierarchyNode child : node.getChildren()) {

+			progress = writeHierarchy(child, myId, progress, maxProgress, spacekey);

+			if (!this.running) {

+				this.feedback = Feedback.CANCELLED;

+				return progress;

+			}

+		}

+

+		return progress;

+	}

 

 	/**

 	 * sends upload progress messages to the feedback window and the log 

@@ -1706,63 +1783,63 @@
 	 */

 	private void logProgressMessage(int current, int max) {

 		this.state.updateProgress();

-        String message = "Uploaded " + current + " out of " + (max + this.newNodes) + " pages.";

-        

-        //more visible note if current is divisible by 10 or last page

-        if ((current % 10) == 0 || current == (max + this.newNodes)) { 

-        	this.state.updateNote(message);

-        	log.info(message);

-        }

-        else { //less visible note for everything else

-        	log.debug(message);

-        }

+		String message = "Uploaded " + current + " out of " + (max + this.newNodes) + " pages.";

+

+		//more visible note if current is divisible by 10 or last page

+		if ((current % 10) == 0 || current == (max + this.newNodes)) { 

+			this.state.updateNote(message);

+			log.info(message);

+		}

+		else { //less visible note for everything else

+			log.debug(message);

+		}

 	}

 

-    /**

-     * sends page, using settings from  the given settings

-     * @param page

-     * @param parentId

-     * @return page id

-     * @throws IllegalArgumentException if a confluenceSetting is invalid

-     */

-    protected String sendPage(Page page, String parentId, UWCUserSettings settings) {

-    	//write current page

-    	//XXX why are we setting these up every page. Most of these are global. 

-    	//XXX If we set these up earlier in the process, we could do the checkConfluenceSettings call 

-    	//(currently in the next sendPage) earlier in the process as well

-    	ConfluenceServerSettings confSettings = getConfluenceServerSettings(settings);

-    	

-    	//check to see if we've assigned a space to the page

-    	if (page.getSpacekey() != null && !"".equals(page.getSpacekey())) { 

-    		confSettings.spaceKey = page.getSpacekey();

-    		String[] spacedata = page.getSpaceData(page.getSpacekey());

-    		String spacename = (spacedata == null || spacedata.length < 1)?page.getSpacekey():spacedata[0];

-    		String spacedesc = (spacedata == null || spacedata.length < 2)?"":spacedata[1];

-    		if (!createSpace(confSettings, spacename, spacedesc, page.isPersonalSpace(), page.getPersonalSpaceUsername())) {

-    			log.warn("Could not create space '" + confSettings.spaceKey + "' assigned to page '" + page.getName() + "'. " +

-    					"Using default space from settings.");

-    			confSettings.spaceKey = settings.getSpace();

-    		}

-    	} // check to see if we're automatically detecting spaces based on the file system

-    	else if (isAutoDetectingSpacekeys()) { 

-    		confSettings.spaceKey = determineSpaceKey(page);

-    		if ("".equals(confSettings.spaceKey) || confSettings.spaceKey == null) {

-    			String error = "Could not find spacekeys. Note: the auto-detect spacekeys" +

-    					" framework is being used. You must choose directories not individual files" +

-    					" for conversion.\nCannot upload files to Confluence. Exiting.";

-    			log.error(error);

-    			this.errors.addError(Feedback.BAD_SPACE, error, true);

-    			this.state.updateProgress(this.state.getMax());

-    			this.running = false;

-    			return "";

-    		}

-    		if (!createSpace(confSettings)) return null;

-    	} //else otherwise use the default (settings based) spacekey

-    		

-    	return sendPage(page, parentId, confSettings);

-    }

+	/**

+	 * sends page, using settings from  the given settings

+	 * @param page

+	 * @param parentId

+	 * @return page id

+	 * @throws IllegalArgumentException if a confluenceSetting is invalid

+	 */

+	protected String sendPage(Page page, String parentId, UWCUserSettings settings) {

+		//write current page

+		//XXX why are we setting these up every page. Most of these are global. 

+		//XXX If we set these up earlier in the process, we could do the checkConfluenceSettings call 

+		//(currently in the next sendPage) earlier in the process as well

+		ConfluenceServerSettings confSettings = getConfluenceServerSettings(settings);

 

-    Pattern spacepermPattern = Pattern.compile("[{]groupname[}](.*?)[{]permissions[}](.*)");

+		//check to see if we've assigned a space to the page

+		if (page.getSpacekey() != null && !"".equals(page.getSpacekey())) { 

+			confSettings.spaceKey = page.getSpacekey();

+			String[] spacedata = page.getSpaceData(page.getSpacekey());

+			String spacename = (spacedata == null || spacedata.length < 1)?page.getSpacekey():spacedata[0];

+			String spacedesc = (spacedata == null || spacedata.length < 2)?"":spacedata[1];

+			if (!createSpace(confSettings, spacename, spacedesc, page.isPersonalSpace(), page.getPersonalSpaceUsername())) {

+				log.warn("Could not create space '" + confSettings.spaceKey + "' assigned to page '" + page.getName() + "'. " +

+						"Using default space from settings.");

+				confSettings.spaceKey = settings.getSpace();

+			}

+		} // check to see if we're automatically detecting spaces based on the file system

+		else if (isAutoDetectingSpacekeys()) { 

+			confSettings.spaceKey = determineSpaceKey(page);

+			if ("".equals(confSettings.spaceKey) || confSettings.spaceKey == null) {

+				String error = "Could not find spacekeys. Note: the auto-detect spacekeys" +

+						" framework is being used. You must choose directories not individual files" +

+						" for conversion.\nCannot upload files to Confluence. Exiting.";

+				log.error(error);

+				this.errors.addError(Feedback.BAD_SPACE, error, true);

+				this.state.updateProgress(this.state.getMax());

+				this.running = false;

+				return "";

+			}

+			if (!createSpace(confSettings)) return null;

+		} //else otherwise use the default (settings based) spacekey

+

+		return sendPage(page, parentId, confSettings);

+	}

+

+	Pattern spacepermPattern = Pattern.compile("[{]groupname[}](.*?)[{]permissions[}](.*)");

 	private void updateSpacePermissions(ConfluenceServerSettings confSettings) {

 		if (!this.miscProperties.containsKey(PROPKEY_SPACEPERMS)) return;

 

@@ -1794,8 +1871,8 @@
 				String message = "Could not update permissions ('"+allperms+"') for groupname: '" + groupname +"'";

 				getErrors().addError(Feedback.REMOTE_API_ERROR,

 						message,

-    					true);

-    			log.error(message,e);

+						true);

+				log.error(message,e);

 			}

 		}

 	}

@@ -1803,90 +1880,90 @@
 	public ConfluenceServerSettings getConfluenceServerSettings(

 			UWCUserSettings settings) {

 		ConfluenceServerSettings confSettings = new ConfluenceServerSettings(); 

-    	confSettings.login = settings.getLogin();

-    	confSettings.password = settings.getPassword();

-    	confSettings.url = settings.getUrl(); 

-    	confSettings.spaceKey = settings.getSpace();

-    	confSettings.truststore = settings.getTruststore();

-    	confSettings.trustpass = settings.getTrustpass();

-    	confSettings.trustallcerts = settings.getTrustall();

+		confSettings.login = settings.getLogin();

+		confSettings.password = settings.getPassword();

+		confSettings.url = settings.getUrl(); 

+		confSettings.spaceKey = settings.getSpace();

+		confSettings.truststore = settings.getTruststore();

+		confSettings.trustpass = settings.getTrustpass();

+		confSettings.trustallcerts = settings.getTrustall();

 		return confSettings;

 	}

-    

-    /**

-     * creates a space for the spacekey in the given settings, if it

-     * doesn't already exist

-     * @param confSettings

-     * @return false if space could not be created

-     */

-    protected boolean createSpace(ConfluenceServerSettings confSettings) {

-    	String spaceName = confSettings.spaceKey;

-    	String description = "This space was auto-generated by the UWC.";

-    	return createSpace(confSettings, spaceName, description, false, null);

-    }

-    /**

-     * creates a space for the spacekey in the given settings, if it doesn't already exist

-     * @param confSettings 

-     * @param name name of space to be created. will not be used, if space already exists

-     * @param description description of space to be created. will not be used if space already exists

-     * @param isPersonalSpace true if space is personal space

-     * @param personalSpaceUsername should be non-null if isPersonalSpace is true. If this condition is not

-     * maintained, will use spacekey in confSettings instead

-     * @return false if space could not be created

-     */

-    protected boolean createSpace(ConfluenceServerSettings confSettings, String name, String description, 

-    		boolean isPersonalSpace, String personalSpaceUsername) {

-    	String spacekey = confSettings.spaceKey;

-    	RemoteWikiBroker broker = RemoteWikiBroker.getInstance();

-    	SpaceForXmlRpc space = broker.createSpace(spacekey, name, description);

-    	if (isPersonalSpace) {

-    		space.setType(SpaceType.PERSONAL);

-    		space.setUsername(personalSpaceUsername);

-    	}

 

-    	try {

-    		// Conf 2.x will throw an exception if the space doesn't exist.

-    		SpaceForXmlRpc space2 = broker.getSpace(confSettings, spacekey); 

-    		// Conf 3.x will not throw an exception but will return null

-    		if (space2 == null) { //Confluence 3.x

-    			String note = "Creating space with spacekey '" + spacekey + "' and name: " + name;

+	/**

+	 * creates a space for the spacekey in the given settings, if it

+	 * doesn't already exist

+	 * @param confSettings

+	 * @return false if space could not be created

+	 */

+	protected boolean createSpace(ConfluenceServerSettings confSettings) {

+		String spaceName = confSettings.spaceKey;

+		String description = "This space was auto-generated by the UWC.";

+		return createSpace(confSettings, spaceName, description, false, null);

+	}

+	/**

+	 * creates a space for the spacekey in the given settings, if it doesn't already exist

+	 * @param confSettings 

+	 * @param name name of space to be created. will not be used, if space already exists

+	 * @param description description of space to be created. will not be used if space already exists

+	 * @param isPersonalSpace true if space is personal space

+	 * @param personalSpaceUsername should be non-null if isPersonalSpace is true. If this condition is not

+	 * maintained, will use spacekey in confSettings instead

+	 * @return false if space could not be created

+	 */

+	protected boolean createSpace(ConfluenceServerSettings confSettings, String name, String description, 

+			boolean isPersonalSpace, String personalSpaceUsername) {

+		String spacekey = confSettings.spaceKey;

+		RemoteWikiBroker broker = RemoteWikiBroker.getInstance();

+		SpaceForXmlRpc space = broker.createSpace(spacekey, name, description);

+		if (isPersonalSpace) {

+			space.setType(SpaceType.PERSONAL);

+			space.setUsername(personalSpaceUsername);

+		}

+

+		try {

+			// Conf 2.x will throw an exception if the space doesn't exist.

+			SpaceForXmlRpc space2 = broker.getSpace(confSettings, spacekey); 

+			// Conf 3.x will not throw an exception but will return null

+			if (space2 == null) { //Confluence 3.x

+				String note = "Creating space with spacekey '" + spacekey + "' and name: " + name;

 				log.info(note);

-    			this.state.updateNote(note);

-    			try {

-    				broker.addSpace(confSettings, space);

-    				//at some point in Confluence 4.x, the Home page stopped being set to Home, so let's prestore the homepage id

-    				Vector newspacepages = broker.getAllServerPageSummaries(confSettings, space.getSpaceKey());

-    				PageForXmlRpc newhome = (PageForXmlRpc) newspacepages.get(0); //should only be one at this point

-    				this.homepages.put(space.getSpaceKey(), newhome.getId());

-    				//check to see if we're setting any permissions

-    				updateSpacePermissions(confSettings);

-    			} catch (Exception e) {

-        			getErrors().addError(Feedback.BAD_LOGIN, 

-        					"Could not create space: " + spacekey +

-        					" with login: " + confSettings.login +

-        					". That login may not have permission to create spaces.", 

-        					true);

-        			e.printStackTrace();

-        			return false;

-    			}

-    		}

-    	} catch (Exception e) { //Confluence 2.x

-    		try { //exception! So, try adding the space

-    			String note = "Creating space with spacekey '" + spacekey + "' and name: " + name;

+				this.state.updateNote(note);

+				try {

+					broker.addSpace(confSettings, space);

+					//at some point in Confluence 4.x, the Home page stopped being set to Home, so let's prestore the homepage id

+					Vector newspacepages = broker.getAllServerPageSummaries(confSettings, space.getSpaceKey());

+					PageForXmlRpc newhome = (PageForXmlRpc) newspacepages.get(0); //should only be one at this point

+					this.homepages.put(space.getSpaceKey(), newhome.getId());

+					//check to see if we're setting any permissions

+					updateSpacePermissions(confSettings);

+				} catch (Exception e) {

+					getErrors().addError(Feedback.BAD_LOGIN, 

+							"Could not create space: " + spacekey +

+							" with login: " + confSettings.login +

+							". That login may not have permission to create spaces.", 

+							true);

+					e.printStackTrace();

+					return false;

+				}

+			}

+		} catch (Exception e) { //Confluence 2.x

+			try { //exception! So, try adding the space

+				String note = "Creating space with spacekey '" + spacekey + "' and name: " + name;

 				log.info(note);

-    			this.state.updateNote(note);

-    			broker.addSpace(confSettings, space);

-    		} catch (Exception e1) { //something bad happened.

-    			getErrors().addError(Feedback.BAD_LOGIN, 

-    					"Could not create space: " + spacekey +

-    					" with login: " + confSettings.login +

-    					". That login may not have permission to create spaces.", 

-    					true);

-    			e1.printStackTrace();

-    			return false;

-    		}

-    	}

-    	return true;

+				this.state.updateNote(note);

+				broker.addSpace(confSettings, space);

+			} catch (Exception e1) { //something bad happened.

+				getErrors().addError(Feedback.BAD_LOGIN, 

+						"Could not create space: " + spacekey +

+						" with login: " + confSettings.login +

+						". That login may not have permission to create spaces.", 

+						true);

+				e1.printStackTrace();

+				return false;

+			}

+		}

+		return true;

 	}

 

 	/**

@@ -1940,7 +2017,7 @@
 			newPage = broker.storeNewOrUpdatePage(confSettings, confSettings.spaceKey, brokerPage);

 			if (newPage == null || newPage.getPageParams() == null) {

 				String message = "Unknown problem occured while sending page '" + pageTable.get("title") +

-				"'. See atlassian-confluence.log for more details.";

+						"'. See atlassian-confluence.log for more details.";

 				log.error(message);

 				getErrors().addError(Feedback.REMOTE_API_ERROR, message, true);

 				return null;

@@ -1948,12 +2025,13 @@
 		} catch (Exception e) {

 			getErrors().addError(Feedback.REMOTE_API_ERROR, 

 					"The Remote API threw an exception when it tried to upload page: \"" +

-					pageTable.get("title") +

-					"\".", true);

+							pageTable.get("title") +

+							"\".", true);

 			e.printStackTrace();

 			return null;

-		}	

-		

+		}

+		log.debug("Page URL: " + newPage.getUrl());

+

 		//move the page if necessary and you can

 		log.debug("Identifying parent location for page...");

 		String parentid = null;

@@ -1975,22 +2053,22 @@
 					this.homepages.put(confSettings.spaceKey, "-1");

 				}

 			}

-			

+

 		}

 		if (parentid != null) {

 			log.debug("Attempting to set parent to: " + parentid);

 			try {

 				broker.movePage(confSettings, newPage.getId(), parentid, RemoteWikiBroker.Position.APPEND);

 			} catch (Exception e) {

-				log.debug("Could not move page " + pageTable.get("title") + "\n" + e.getMessage() + "\n" +

-							e.getStackTrace()); //could be because Confluence is earlier than 2.9

+				log.error("Could not move page " + pageTable.get("title") + "\n" + e.getMessage() + "\n" +

+						e.getStackTrace()); //could be because Confluence is earlier than 2.9

 			}

 		}

-		

-    	return newPage.getId();

-		

+

+		return newPage.getId();

+

 	}

-	

+

 	/*********

 	 * to send a blog table which has all the attributes for a blog to wiki

 	 * @param broker

@@ -2006,7 +2084,7 @@
 			newPage = broker.storeBlog(confSettings, confSettings.spaceKey, brokerPage);

 			if (newPage == null || newPage.getblogParams() == null) {

 				String message = "Unknown problem occured while sending page '" + pageTable.get("title") +

-				"'. See atlassian-confluence.log for more details.";

+						"'. See atlassian-confluence.log for more details.";

 				log.error(message);

 				getErrors().addError(Feedback.REMOTE_API_ERROR, message, true);

 				return null;

@@ -2014,77 +2092,82 @@
 		} catch (Exception e) {

 			getErrors().addError(Feedback.REMOTE_API_ERROR, 

 					"The Remote API threw an exception when it tried to upload page: \"" +

-					pageTable.get("title") +

-					"\".", true);

+							pageTable.get("title") +

+							"\".", true);

 			e.printStackTrace();

 			return null;

 		}	

-    	return newPage.getId();

+		log.debug("Page URL: " + newPage.getUrl());

+		return newPage.getId();

 	}

-	

-	

+

+

 	protected void checkConfluenceSettings(ConfluenceServerSettings confSettings)

 	{

 		//check for problems with settings 

-    	Feedback testConnectionFeedback = TestSettingsListener.getConnectionFeedback(confSettings, isAutoDetectingSpacekeys());

-    	if (testConnectionFeedback != Feedback.OK) {

-    		String message = TestSettingsListener.getConnectionFeedbackMessage(confSettings, isAutoDetectingSpacekeys());

-    		log.error(message);

-    		this.state.updateNote(message);

-    		throw new IllegalArgumentException(message);

-    	}

-//    	log.info(TestSettingsListener.SUCCESS_MESSAGE_LONG); //this is getting called for every page 

-		

+		Feedback testConnectionFeedback = TestSettingsListener.getConnectionFeedback(confSettings, isAutoDetectingSpacekeys());

+		if (testConnectionFeedback != Feedback.OK) {

+			String message = TestSettingsListener.getConnectionFeedbackMessage(confSettings, isAutoDetectingSpacekeys());

+			log.error(message);

+			this.state.updateNote(message);

+			throw new IllegalArgumentException(message);

+		}

+		//    	log.info(TestSettingsListener.SUCCESS_MESSAGE_LONG); //this is getting called for every page 

+

 	}

 	/**

-     * sends page using the given settings

-     * @param page

-     * @param parentId

-     * @param confSettings

-     * @return page id the confluence page id for the page being stored. Used if

-     * page is new.

-     */

-    protected String sendPage(Page page, String parentId, ConfluenceServerSettings confSettings) {

-    	//create wiki broker

-    	RemoteWikiBroker broker = RemoteWikiBroker.getInstance();

-    	//update page content to be xhtml

-    	if (Boolean.parseBoolean(this.miscProperties.getProperty("engine-markuptoxhtml", "true"))) {

-    		page = pageContentToXhtml(broker, confSettings, page);

-    	} else {

-    		log.debug("Engine: markup to xhtml property set to false");

-    	}

-    	//create page that broker can use

-    	Hashtable pageTable = createPageTable(page, parentId);

-     	//check for problems with settings 

-    	checkConfluenceSettings(confSettings); //XXX Why are we doing this for every page? 'cause we seem to create the confSettings on a page by page basis?

-    	//write ancestors, if any, first

-    	if (page.getAncestors() != null && !page.getAncestors().isEmpty()) {

-    		pageTable = handleAncestors(page, confSettings, pageTable);

-    	}

-    	//send page

-    	String id = null;

-    	if (page.isBlog()) {

-    		log.debug("Attempting to send blog: " + page.getName() + " to space: " + confSettings.spaceKey);

-    		id = sendBlog(broker, pageTable, confSettings);

-    	} else { 

-    		log.debug("Attempting to send page: " + page.getName() + " to space: " + confSettings.spaceKey);

-    		id = sendPage(broker, pageTable, confSettings);

-    	}

-    	if (id == null) return null;

-     	//send attachments

-    	sendAttachments(page, broker, id, confSettings);

-    	//send labels 

-    	sendLabels(page, broker, id, confSettings);

-    	//send comments

-    	sendComments(page, broker, id, confSettings);

-    	//set author

-    	log.debug("Page Version: " + page.getVersion());

-    	sendAuthor(page, broker, id, confSettings);

-    	//set timestamp

-    	sendTimestamp(page, broker, id, confSettings);

-    	//return the page id

-    	return id;

-    }

+	 * sends page using the given settings

+	 * @param page

+	 * @param parentId

+	 * @param confSettings

+	 * @return page id the confluence page id for the page being stored. Used if

+	 * page is new.

+	 */

+	protected String sendPage(Page page, String parentId, ConfluenceServerSettings confSettings) {

+		//create wiki broker

+		RemoteWikiBroker broker = RemoteWikiBroker.getInstance();

+		//update page content to be xhtml

+		if (Boolean.parseBoolean(this.miscProperties.getProperty("engine-markuptoxhtml", "true"))) {

+			page = pageContentToXhtml(broker, confSettings, page);

+		} else {

+			log.debug("Engine: markup to xhtml property set to false");

+		}

+		//create page that broker can use

+		Hashtable pageTable = createPageTable(page, parentId);

+		//check for problems with settings 

+		checkConfluenceSettings(confSettings); //XXX Why are we doing this for every page? 'cause we seem to create the confSettings on a page by page basis?

+		//write ancestors, if any, first

+		if (page.getAncestors() != null && !page.getAncestors().isEmpty()) {

+			pageTable = handleAncestors(page, confSettings, pageTable);

+		}

+		//send page

+		String id = null;

+		if (!(page instanceof VersionPage) && page.getFile() != null) {

+			log.debug("Original Filepath: " + page.getFile().getAbsolutePath());

+		}

+		String tmpspacekey = (page.getSpacekey()!=null)?page.getSpacekey():confSettings.spaceKey;

+		if (page.isBlog()) {

+			log.debug("Attempting to send blog: " + page.getName() + " to space: " + tmpspacekey);

+			id = sendBlog(broker, pageTable, confSettings);

+		} else { 

+			log.debug("Attempting to send page: " + page.getName() + " to space: " + tmpspacekey);

+			id = sendPage(broker, pageTable, confSettings);

+		}

+		if (id == null) return null;

+		//send attachments

+		sendAttachments(page, broker, id, confSettings);

+		//send labels 

+		sendLabels(page, broker, id, confSettings);

+		//send comments

+		sendComments(page, broker, id, confSettings);

+		//set author

+		log.debug("Page Version: " + page.getVersion());

+		sendAuthor(page, broker, id, confSettings);

+		//set timestamp

+		sendTimestamp(page, broker, id, confSettings);

+		//return the page id

+		return id;

+	}

 

 	private Hashtable handleAncestors(Page page,

 			ConfluenceServerSettings confSettings, Hashtable pageTable) {

@@ -2095,16 +2178,17 @@
 			enforceBlogId(page, page.getAncestors(), blogid);

 			pageTable.put("id", blogid);

 		}

-		writePages(page.getAncestors(), settings.getSpace());

+		if (page.getAncestors() != null) log.info("Number of ancestors for page '"+page.getName()+"': " + page.getAncestors().size());

+		writePages(page.getAncestors(), settings.getSpace(), false);

 		return pageTable;

 	}

-    

-    private void enforceBlogId(Page page, Vector<VersionPage> pages,

+

+	private void enforceBlogId(Page page, Vector<VersionPage> pages,

 			String blogid) {

-    	page.setId(blogid);

-    	for (VersionPage anc : pages) {

-    		anc.setId(blogid);

-    	}

+		page.setId(blogid);

+		for (VersionPage anc : pages) {

+			anc.setId(blogid);

+		}

 	}

 

 	private void enforceAncestorTitleAndKey(Vector<VersionPage> pages,

@@ -2114,22 +2198,22 @@
 			page.setSpacekey(spacekey);

 			page.setIsBlog(isBlog);

 		}

-		

+

 	}

 

 	public String markupToXhtml(String markup) {

-    	RemoteWikiBroker broker = RemoteWikiBroker.getInstance();

-    	ConfluenceServerSettings confSettings = getConfluenceServerSettings(this.settings);

-    	try {

+		RemoteWikiBroker broker = RemoteWikiBroker.getInstance();

+		ConfluenceServerSettings confSettings = getConfluenceServerSettings(this.settings);

+		try {

 			return getContentAsXhtmlFormat(broker, confSettings, markup);

-    	} catch (Exception e) {

-    		String errorMessage = "Could not transform wiki content from markup to xhtml.";

-    		log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage);

-    		this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

-    		return null;

-    	}

-    }

-    

+		} catch (Exception e) {

+			String errorMessage = "Could not transform wiki content from markup to xhtml.";

+			log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage);

+			this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

+			return null;

+		}

+	}

+

 	private Page pageContentToXhtml(RemoteWikiBroker broker,

 			ConfluenceServerSettings confSettings, Page page) {

 		try {

@@ -2138,132 +2222,132 @@
 		} catch (Exception e) {

 			String errorMessage = "Could not transform wiki content in page: '"+page.getName()+

 					"' from markup to xhtml.";

-    		log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage);

-    		this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

+			log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage);

+			this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

 		}

 		return page;

 	}

 

 	/**

-     * creates a parameter table with the given page and parentId.

-     * @param page

-     * @param parentId

-     * @return table with Remote API page parameters

-     */

-    private Hashtable createPageTable(Page page, String parentId) {

-    	Hashtable table = new Hashtable();

-    	table.put("content", page.getConvertedText());

-    	table.put("title", page.getName()); 

-    	if (parentId != null && !parentId.equals("null")) table.put("parentId", parentId);

-    	if (page.getVersion() > 0) table.put("version", page.getVersion() + "");

-    	if (page.isBlog() && page.getId() != null) table.put("id", page.getId());

-    	return table;

+	 * creates a parameter table with the given page and parentId.

+	 * @param page

+	 * @param parentId

+	 * @return table with Remote API page parameters

+	 */

+	private Hashtable createPageTable(Page page, String parentId) {

+		Hashtable table = new Hashtable();

+		table.put("content", page.getConvertedText());

+		table.put("title", page.getName()); 

+		if (parentId != null && !parentId.equals("null")) table.put("parentId", parentId);

+		if (page.getVersion() > 0) table.put("version", page.getVersion() + "");

+		if (page.isBlog() && page.getId() != null) table.put("id", page.getId());

+		return table;

 	}

 

-    /**

-     * checks the given page for valid attachments,

-     * and sends them to Confluence using the rwb and the given pageId string as the 

-     * page to be attached to.

-     * 

-     * @param page given page object that might have attachments

-     * @param broker XML-RPC broker which will communicate with Confluence

-     * @param pageId the page the Confluence will attach the attachment to

-     * @param confSettings the confluence user settings needed by the broker to connect to Confluence 

-     */

-    private void sendAttachments(Page page, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings) {

-    	log.debug("Examining attachments for page: " + page.getName());

+	/**

+	 * checks the given page for valid attachments,

+	 * and sends them to Confluence using the rwb and the given pageId string as the 

+	 * page to be attached to.

+	 * 

+	 * @param page given page object that might have attachments

+	 * @param broker XML-RPC broker which will communicate with Confluence

+	 * @param pageId the page the Confluence will attach the attachment to

+	 * @param confSettings the confluence user settings needed by the broker to connect to Confluence 

+	 */

+	private void sendAttachments(Page page, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings) {

+		log.debug("Examining attachments for page: " + page.getName());

 		// Send the attachments

-        for (Attachment attachment : page.getAllAttachmentData()) {

-        	if (alreadyAttached(page, attachment.getFile()))

-        		continue;

-        	sendAttachment(attachment, broker, pageId, confSettings);

-        }

+		for (Attachment attachment : page.getAllAttachmentData()) {

+			if (alreadyAttached(page, attachment.getFile()))

+				continue;

+			sendAttachment(attachment, broker, pageId, confSettings);

+		}

 	}

-    

-    protected void sendLabels(Page page, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings) {

-    	log.debug("Examining labels for page: " + page.getName());

-    	//check to see if we're sending labels for this version of the page

+

+	protected void sendLabels(Page page, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings) {

+		log.debug("Examining labels for page: " + page.getName());

+		//check to see if we're sending labels for this version of the page

 		if (badVersionForSendingLabels(page)) return;

-    	String labels = page.getLabelsAsString();

-    	log.debug("Sending Labels: " + labels);

-    	if (labels == null) 

-    		return;

-    	try {

-    		broker.addLabels(confSettings, labels, pageId);

-    	} catch (Exception e) {

-    		String errorMessage = "Could not add labels '" + labels + "' to page '" + page.getName() +"'";

-    		log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage);

-    		this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

-    	}

-    }

+		String labels = page.getLabelsAsString();

+		log.debug("Sending Labels: " + labels);

+		if (labels == null) 

+			return;

+		try {

+			broker.addLabels(confSettings, labels, pageId);

+		} catch (Exception e) {

+			String errorMessage = "Could not add labels '" + labels + "' to page '" + page.getName() +"'";

+			log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage);

+			this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

+		}

+	}

 

 	private boolean badVersionForSendingLabels(Page page) {

 		int version = page.getVersion();

-    	int latest = Page.getLatestVersion(page.getName());

-    	boolean history = isHandlingPageHistories();

-    	String allVersionsProp = (String) this.miscProperties.get("page-history-allversionlabels");

-    	boolean allVersions = (allVersionsProp != null) && Boolean.parseBoolean(allVersionsProp);

+		int latest = Page.getLatestVersion(page.getName());

+		boolean history = isHandlingPageHistories();

+		String allVersionsProp = (String) this.miscProperties.get("page-history-allversionlabels");

+		boolean allVersions = (allVersionsProp != null) && Boolean.parseBoolean(allVersionsProp);

 		return history && !allVersions && version != latest;

 	}

-    

-    /**

-     * adds page comments to a page in confluence

-     * @param page given page object that might have comments

-     * @param broker XML-RPC broker which will communicate with Confluence

-     * @param pageId the page id for the given page

-     * @param confSettings the confluence user settings needed by the broker to connect to Confluence

-     */

-    protected void sendComments(Page page, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings) {

-    	if (page.hasComments()) {

-    		log.debug("Sending comments for page: " + page.getName());

-    		try {

-    			for (Comment comment : page.getAllCommentData()) {

-    				if (comment == null) {

-    					log.error("Comment was null! SKIPPING");

-    					this.errors.addError(Feedback.CONVERTER_ERROR, "Comment should not be null!", true);

-    					continue; 

-    				}

-    				//create page that broker can use

-    				CommentForXmlRpc brokerComment = new CommentForXmlRpc();

-    				brokerComment.setPageId(pageId);

-    				String commentcontent = comment.text;

-    				if (!comment.isXhtml()) {

+

+	/**

+	 * adds page comments to a page in confluence

+	 * @param page given page object that might have comments

+	 * @param broker XML-RPC broker which will communicate with Confluence

+	 * @param pageId the page id for the given page

+	 * @param confSettings the confluence user settings needed by the broker to connect to Confluence

+	 */

+	protected void sendComments(Page page, RemoteWikiBroker broker, String pageId, ConfluenceServerSettings confSettings) {

+		if (page.hasComments()) {

+			log.debug("Sending comments for page: " + page.getName());

+			try {

+				for (Comment comment : page.getAllCommentData()) {

+					if (comment == null) {

+						log.error("Comment was null! SKIPPING");

+						this.errors.addError(Feedback.CONVERTER_ERROR, "Comment should not be null!", true);

+						continue; 

+					}

+					//create page that broker can use

+					CommentForXmlRpc brokerComment = new CommentForXmlRpc();

+					brokerComment.setPageId(pageId);

+					String commentcontent = comment.text;

+					if (!comment.isXhtml()) {

 						commentcontent = getContentAsXhtmlFormat(broker, confSettings, comment.text);

 					}

-    				brokerComment.setContent(commentcontent);

-    				//upload comment

-    				CommentForXmlRpc uploadedComment = broker.addComment(confSettings, brokerComment);

-    				if (comment.hasCreator()) {

-    					boolean usersMustExist = false;

+					brokerComment.setContent(commentcontent);

+					//upload comment

+					CommentForXmlRpc uploadedComment = broker.addComment(confSettings, brokerComment);

+					if (comment.hasCreator()) {

+						boolean usersMustExist = false;

 						broker.setCreator(confSettings, comment.creator, uploadedComment.getId(), usersMustExist);

 						broker.setLastModifier(confSettings, comment.creator, uploadedComment.getId(), usersMustExist);

-    				}

-    				if (comment.hasTimestamp()) {

-    					broker.setCreateDate(confSettings, comment.timestamp, uploadedComment.getId());

-    					broker.setLastModifiedDate(confSettings, comment.timestamp, uploadedComment.getId());

-    				}

-    			}

-    		} catch (Exception e) {

-    			String errorMessage = null;

-    			if (e.getMessage() == null) {

-    				log.error("Problem with comments!", e);

-    				return;

-    			}

-    			else if (e.getMessage().contains("NotPermittedException")) {

-    				errorMessage = "User is not permitted to add comments to page: " + page.getName() + "'";

-    			}

-    			else if (e.getMessage().contains("does not exist")) {

-    				errorMessage = "Cannot add comments to the page because it does not exist: " + page.getName();

-    			}

-    			else {

-    				errorMessage = "Could not send comments to page '" + page.getName() +"'";

-    			}

-    			log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage);

-    			this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

-    		}

-    	}

-//    	else log.debug("Page has no comments."); //DELETE

-    }

+					}

+					if (comment.hasTimestamp()) {

+						broker.setCreateDate(confSettings, comment.timestamp, uploadedComment.getId());

+						broker.setLastModifiedDate(confSettings, comment.timestamp, uploadedComment.getId());

+					}

+				}

+			} catch (Exception e) {

+				String errorMessage = null;

+				if (e.getMessage() == null) {

+					log.error("Problem with comments!", e);

+					return;

+				}

+				else if (e.getMessage().contains("NotPermittedException")) {

+					errorMessage = "User is not permitted to add comments to page: " + page.getName() + "'";

+				}

+				else if (e.getMessage().contains("does not exist")) {

+					errorMessage = "Cannot add comments to the page because it does not exist: " + page.getName();

+				}

+				else {

+					errorMessage = "Could not send comments to page '" + page.getName() +"'";

+				}

+				log.error(Feedback.REMOTE_API_ERROR + ": " + errorMessage);

+				this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

+			}

+		}

+		//    	else log.debug("Page has no comments."); //DELETE

+	}

 

 	public String getContentAsXhtmlFormat(RemoteWikiBroker broker, ConfluenceServerSettings confSettings, String text) throws XmlRpcException, IOException {

 		return broker.convertWikiToStorageFormat(confSettings, text);

@@ -2283,12 +2367,12 @@
 			} catch (Exception e) {

 				String errorMessage = Feedback.REMOTE_API_ERROR + ": Problem setting creator or last modifier data.";

 				log.error(errorMessage);

-    			this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

+				this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

 				e.printStackTrace();

 			}

 		}

 	}

-	

+

 	private void sendTimestamp(Page page, RemoteWikiBroker broker, String id, ConfluenceServerSettings confSettings) {

 		if (page.getTimestamp() != null) {

 			log.debug("Sending timestamp data: " + page.getTimestamp());

@@ -2299,7 +2383,7 @@
 					timezone = timezone.trim();

 					dateFormat.setTimeZone(TimeZone.getTimeZone(timezone));

 				}

-			    String timestamp = dateFormat.format(page.getTimestamp());

+				String timestamp = dateFormat.format(page.getTimestamp());

 

 				if (page.getVersion() == 1) { //only set the creator if its the first version

 					broker.setCreateDate(confSettings, timestamp, id);

@@ -2309,18 +2393,18 @@
 			} catch (Exception e) {

 				String errorMessage = Feedback.REMOTE_API_ERROR + ": Problem setting create or last modified date.";

 				log.error(errorMessage);

-    			this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

+				this.errors.addError(Feedback.REMOTE_API_ERROR, errorMessage, true);

 				e.printStackTrace();

 			}

 		}

 	}

 

-    

-    /**

-     * @param file

-     * @return true if the given file does not exist on the filesystem.

-     */

-    protected boolean doesNotExist(File file) {

+

+	/**

+	 * @param file

+	 * @return true if the given file does not exist on the filesystem.

+	 */

+	protected boolean doesNotExist(File file) {

 		boolean doesNotExist = !file.exists();

 		if (doesNotExist)

 			log.warn("File '" + file.getPath() + "' does not exist: Skipping");

@@ -2328,16 +2412,16 @@
 	}

 

 	/**

-     * @param file

-     * @return true if file size is too big

-     */

-    protected boolean tooBig(File file) {

-    	

-    	if (!file.exists()) return false;

+	 * @param file

+	 * @return true if file size is too big

+	 */

+	protected boolean tooBig(File file) {

+

+		if (!file.exists()) return false;

 		int length = (int) file.length();

-		

+

 		String maxString = getMaxAttachmentSizeString();

-		

+

 		int maxBytes = getAsBytes(maxString);

 		if (maxBytes < 0) return false;

 		boolean tooBig = length > maxBytes;

@@ -2361,83 +2445,83 @@
 	}

 

 	/**

-     * @param fileLocation location of properties file

+	 * @param fileLocation location of properties file

 	 * @return a Properties object containing confluence settings

-     */

-    protected Properties loadProperties(String fileLocation) {

-    	/* most of this code grabbed from ConfluenceSettingsForm.populateConfluenceSettings*/

-    	Properties properties = new Properties();

-        File confSettings = new File(fileLocation);

-        if (confSettings.exists()) {

-            // load properties file

-            FileInputStream fis;

-            try {

-                fis = new FileInputStream(fileLocation);

+	 */

+	protected Properties loadProperties(String fileLocation) {

+		/* most of this code grabbed from ConfluenceSettingsForm.populateConfluenceSettings*/

+		Properties properties = new Properties();

+		File confSettings = new File(fileLocation);

+		if (confSettings.exists()) {

+			// load properties file

+			FileInputStream fis;

+			try {

+				fis = new FileInputStream(fileLocation);

 				properties.load(fis);

-                fis.close();

-            } catch (FileNotFoundException e) {

-                e.printStackTrace();

-            } catch (IOException e) {

-                e.printStackTrace();

-            }

-        }

-        return properties;

-    }

-

-    /**

-     * calculates the number of bytes the given maxString represents

-     * @param maxString file size described as a String.

-     * Example: 5B, 5K, 5M, 5G, etc.

-     * @return as Bytes.

-     * Respectively: 5, 5120, 5242880, 5368709120

-     */

-    protected int getAsBytes(String maxString) {

-    	String maxRegex = "^(\\d+)(\\D)";

-    	if (maxString == null || "".equals(maxString)) 

-    		return -1;

-    	

-    	int power, num = 0;

-    	String numString, unitString = null;

-    	if (Pattern.matches("^\\d+$", maxString)) {

-    		unitString = "B";

-    		numString = maxString;

-    	}

-    	else {

-	    	numString = maxString.replaceFirst(maxRegex, "$1");

-	    	unitString = maxString.replaceFirst(maxRegex, "$2");

-    	}

-    	try {

-    		num = Integer.parseInt(numString);

-    	} catch (NumberFormatException e) {

-    		String message = PROP_ATTACHMENT_SIZE_MAX + " setting is malformed.\n" +

-    				"Setting must be formatted like so: [number][unit], where unit is\n" +

-    				"one of the following: B, K, M, G. No max attachment size set.";

-    		log.error(message);

-    		return -1;

-    	}

-    	unitString = unitString.toUpperCase();

-    	char unit = unitString.toCharArray()[0]; //first char in that string

-		

-    	switch (unit) {

-			case ('B'): power = 0;break;

-			case ('K'): power = 1;break;

-			case ('M'): power = 2;break;

-			case ('G'): power = 3;break;

-			default: return -1;

+				fis.close();

+			} catch (FileNotFoundException e) {

+				e.printStackTrace();

+			} catch (IOException e) {

+				e.printStackTrace();

+			}

 		}

-		

+		return properties;

+	}

+

+	/**

+	 * calculates the number of bytes the given maxString represents

+	 * @param maxString file size described as a String.

+	 * Example: 5B, 5K, 5M, 5G, etc.

+	 * @return as Bytes.

+	 * Respectively: 5, 5120, 5242880, 5368709120

+	 */

+	protected int getAsBytes(String maxString) {

+		String maxRegex = "^(\\d+)(\\D)";

+		if (maxString == null || "".equals(maxString)) 

+			return -1;

+

+		int power, num = 0;

+		String numString, unitString = null;

+		if (Pattern.matches("^\\d+$", maxString)) {

+			unitString = "B";

+			numString = maxString;

+		}

+		else {

+			numString = maxString.replaceFirst(maxRegex, "$1");

+			unitString = maxString.replaceFirst(maxRegex, "$2");

+		}

+		try {

+			num = Integer.parseInt(numString);

+		} catch (NumberFormatException e) {

+			String message = PROP_ATTACHMENT_SIZE_MAX + " setting is malformed.\n" +

+					"Setting must be formatted like so: [number][unit], where unit is\n" +

+					"one of the following: B, K, M, G. No max attachment size set.";

+			log.error(message);

+			return -1;

+		}

+		unitString = unitString.toUpperCase();

+		char unit = unitString.toCharArray()[0]; //first char in that string

+

+		switch (unit) {

+		case ('B'): power = 0;break;

+		case ('K'): power = 1;break;

+		case ('M'): power = 2;break;

+		case ('G'): power = 3;break;

+		default: return -1;

+		}

+

 		int multiplier = (int) Math.pow(1024, power);

 		int value = num * multiplier;

 		return value;

 	}

 

 	/**

-     * @param page

-     * @param file

-     * @return true if a particular page already has a particular

-     * file attached.

-     */

-    protected boolean alreadyAttached(Page page, File file) {

+	 * @param page

+	 * @param file

+	 * @return true if a particular page already has a particular

+	 * file attached.

+	 */

+	protected boolean alreadyAttached(Page page, File file) {

 		String pagename = page.getName();

 		String filename = file.getName();

 		boolean isblog = page.isBlog();

@@ -2446,167 +2530,167 @@
 		if (attachedFiles == null) 

 			attachedFiles = new HashSet<String>();

 		boolean attached = attachedFiles.contains(attachmentId);

-		

+

 		if (!attached) {

 			attachedFiles.add(attachmentId);

 			if (attachedPaths == null) attachedPaths = new HashSet<String>();

 			attachedPaths.add(file.getAbsolutePath()); //used with orphan upload checking

 		}

 		else log.debug("Attachment " + filename + " is already attached: Skipping.");

-		

+

 		return attached;

 	}

-    

-    /**

-     * to check if the file has been attached with any pages.

-     * @param fileName

-     * @return

-     */    

-    protected boolean alreadyAttached(String fileName) {

-    	if (this.attachedFiles == null || this.attachedFiles.isEmpty())

-    		return false;

-    	Iterator <String>it=attachedFiles.iterator();

-    	while (it.hasNext())

-    	{

-    		String item=it.next();

-    		int index=item.lastIndexOf(fileName);

-    		if (index < 0)

-    			continue;

-    		if (item.length() - index == fileName.length())

-    			return true;		

-    	}

-    	return false;

-    

-	}

-    

-    protected boolean orphanAlreadyAttached(File file) {

-    	if (this.attachedPaths == null) return false;

-    	return (this.attachedPaths.contains(file.getAbsolutePath()));

-    }

 

-    /**

-     * This method determines the mime type of a file. It uses the file

-     * conf/mime.types to map from the file name extension

-     * to a mime type. The mime type file should be read into the

-     * mimeTypes field before this method is called.

-     *

-     * @param file The file object

-     * @return the mime type of the file.

-     */

-    public static String determineContentType(File file) {

-        if (mimeTypes != null) {

-            return mimeTypes.getContentType(file);

-        } 

-        //else assume it's an image

-        String filename = file.getName();

-        int extensionStart = filename.lastIndexOf(".");

-        if (extensionStart >= 0) {

-            String extension = filename.substring(extensionStart + 1);

-            return "image/" + extension;

-        }

-        // Hmm... No extension. Assume it's a text file.

-        return "text/plain";

-    }

-    

-    Pattern switchPattern = Pattern.compile("switch");

-    Pattern suffixPattern = Pattern.compile("suffix");

-    private boolean handlingPageHistories = false;

+	/**

+	 * to check if the file has been attached with any pages.

+	 * @param fileName

+	 * @return

+	 */    

+//	protected boolean alreadyAttached(String fileName) {

+//		if (this.attachedFiles == null || this.attachedFiles.isEmpty())

+//			return false;

+//		Iterator <String>it=attachedFiles.iterator();

+//		while (it.hasNext())

+//		{

+//			String item=it.next();

+//			int index=item.lastIndexOf(fileName);

+//			if (index < 0)

+//				continue;

+//			if (item.length() - index == fileName.length())

+//				return true;		

+//		}

+//		return false;

+//

+//	}

+

+	protected boolean orphanAlreadyAttached(File file) {

+		if (this.attachedPaths == null) return false;

+		return (this.attachedPaths.contains(file.getAbsolutePath()));

+	}

+

+	/**

+	 * This method determines the mime type of a file. It uses the file

+	 * conf/mime.types to map from the file name extension

+	 * to a mime type. The mime type file should be read into the

+	 * mimeTypes field before this method is called.

+	 *

+	 * @param file The file object

+	 * @return the mime type of the file.

+	 */

+	public static String determineContentType(File file) {

+		if (mimeTypes != null) {

+			return mimeTypes.getContentType(file);

+		} 

+		//else assume it's an image

+		String filename = file.getName();

+		int extensionStart = filename.lastIndexOf(".");

+		if (extensionStart >= 0) {

+			String extension = filename.substring(extensionStart + 1);

+			return "image/" + extension;

+		}

+		// Hmm... No extension. Assume it's a text file.

+		return "text/plain";

+	}

+

+	Pattern switchPattern = Pattern.compile("switch");

+	Pattern suffixPattern = Pattern.compile("suffix");

+	private boolean handlingPageHistories = false;

 	private String pageHistorySuffix = null;

-    

-    /**

-     * set the page history state to reflect the page history property

-     * and associated value that are passed as arguments

-     * @param key

-     * @param value

-     */

-    protected void handlePageHistoryProperty(String key, String value) {

-    	Matcher switchFinder = switchPattern.matcher(key);

-    	if (switchFinder.find()) {

-    		//the default should be false, so it's ok to just parse the string.

-    		this.handlingPageHistories = Boolean.parseBoolean(value);

-    		return;

-    	}

-    	Matcher suffixFinder = suffixPattern.matcher(key);

-    	if (suffixFinder.find()) {

-    		setPageHistorySuffix(value);

-    		return;

-    	}

-    }

-    

-    protected void handleIllegalHandling(String key, String value) {

-    	boolean enabled = false; //default, confluence 4 doesn't appear to need this

-    	value = value.trim();

-    	if ("false".equals(value))

-    		enabled = false;

-    	illegalHandlingEnabled = enabled;

-    }

-    

-    protected void handleAutoDetectSpacekeys(String key, String value) {

-    	boolean enabled = false; //default

-    	value = value.trim();

-    	if ("true".equals(value)) {

-    		enabled = true;

-    	}

-    	autoDetectSpacekeys = enabled;

-    }

-    

-    Pattern miscPropsPattern = Pattern.compile("" +

-    		"\\w+\\.\\d+\\.([^.]+)\\.property"

-    		);

-    protected Properties handleMiscellaneousProperties(String key, String value) {

-    	Matcher miscKeyFinder = miscPropsPattern.matcher(key);

-    	if (miscKeyFinder.matches()) {

-    		String misckey = miscKeyFinder.group(1);

-    		if (this.miscProperties == null)

-    			this.miscProperties = new Properties();

-    		this.miscProperties.put(misckey, value);

-    		log.debug("Miscellaneous Property set: " + misckey + "=" + value);

-    		return this.miscProperties;

-    	}

-    	String error = "Miscellaneous property was detected, " +

-		    			"but key was invalid. Could not instantiate property: " +

-		    			key + "=" + value;

+

+	/**

+	 * set the page history state to reflect the page history property

+	 * and associated value that are passed as arguments

+	 * @param key

+	 * @param value

+	 */

+	protected void handlePageHistoryProperty(String key, String value) {

+		Matcher switchFinder = switchPattern.matcher(key);

+		if (switchFinder.find()) {

+			//the default should be false, so it's ok to just parse the string.

+			this.handlingPageHistories = Boolean.parseBoolean(value);

+			return;

+		}

+		Matcher suffixFinder = suffixPattern.matcher(key);

+		if (suffixFinder.find()) {

+			setPageHistorySuffix(value);

+			return;

+		}

+	}

+

+	protected void handleIllegalHandling(String key, String value) {

+		boolean enabled = false; //default, confluence 4 doesn't appear to need this

+		value = value.trim();

+		if ("false".equals(value))

+			enabled = false;

+		illegalHandlingEnabled = enabled;

+	}

+

+	protected void handleAutoDetectSpacekeys(String key, String value) {

+		boolean enabled = false; //default

+		value = value.trim();

+		if ("true".equals(value)) {

+			enabled = true;

+		}

+		autoDetectSpacekeys = enabled;

+	}

+

+	Pattern miscPropsPattern = Pattern.compile("" +

+			"\\w+\\.\\d+\\.([^.]+)\\.property"

+			);

+	protected Properties handleMiscellaneousProperties(String key, String value) {

+		Matcher miscKeyFinder = miscPropsPattern.matcher(key);

+		if (miscKeyFinder.matches()) {

+			String misckey = miscKeyFinder.group(1);

+			if (this.miscProperties == null)

+				this.miscProperties = new Properties();

+			this.miscProperties.put(misckey, value);

+			log.debug("Miscellaneous Property set: " + misckey + "=" + value);

+			return this.miscProperties;

+		}

+		String error = "Miscellaneous property was detected, " +

+				"but key was invalid. Could not instantiate property: " +

+				key + "=" + value;

 		log.error(error);

 		this.errors.addError(Feedback.BAD_PROPERTY, error, true);

 		return this.miscProperties;

-    }

-    

-    private void addDefaultMiscProperties() {

-    	handleMiscellaneousProperties("Testing.1234.spacekey.property", this.settings.getSpace());

-    }

-    

-    protected void handleFilters(String key, String value) throws InstantiationException, IllegalAccessException {

-    	log.debug("filter property = " + value);

-    	getFilterValues().add(value);

-    }

+	}

+

+	private void addDefaultMiscProperties() {

+		handleMiscellaneousProperties("Testing.1234.spacekey.property", this.settings.getSpace());

+	}

+

+	protected void handleFilters(String key, String value) throws InstantiationException, IllegalAccessException {

+		log.debug("filter property = " + value);

+		getFilterValues().add(value);

+	}

 

 	private Set<String> getFilterValues() {

 		if (this.filterValues == null)

 			this.filterValues = new HashSet<String>();

 		return this.filterValues;

 	}

-    

-    /**

-     * sets up .xmlevent properties

-     * @param key must end in .xmlevent

-     * @param value must follow this format:

-     * <tt>

-     * {tag}tagname{class}classname

-     * </tt>

-     * where tagname is the xml tag to associate the event with (b, for bold)

-     * and classname is the parser that will manage the events for that tag.

-     * tagname can contain a comma-delimited list of tags. For example:

-     * {tag}h1, h2, h3{class}com.example.HeaderParser

-     */

-    private void handleXmlEvents(String key, String value) {

-    	String tag = getXmlEventTag(value);

-    	String classname = getXmlEventClassname(value);

-    	String[] tags = tag.split(",");

-    	for (String onetag : tags) {

-    		onetag = onetag.trim();

+

+	/**

+	 * sets up .xmlevent properties

+	 * @param key must end in .xmlevent

+	 * @param value must follow this format:

+	 * <tt>

+	 * {tag}tagname{class}classname

+	 * </tt>

+	 * where tagname is the xml tag to associate the event with (b, for bold)

+	 * and classname is the parser that will manage the events for that tag.

+	 * tagname can contain a comma-delimited list of tags. For example:

+	 * {tag}h1, h2, h3{class}com.example.HeaderParser

+	 */

+	private void handleXmlEvents(String key, String value) {

+		String tag = getXmlEventTag(value);

+		String classname = getXmlEventClassname(value);

+		String[] tags = tag.split(",");

+		for (String onetag : tags) {

+			onetag = onetag.trim();

 			addOneXmlEvent(onetag, classname);

 		}

-    }

+	}

 

 	/**

 	 * adds one xml event object to the events handler, such that the classname becomes 

@@ -2618,8 +2702,8 @@
 	 */

 	private void addOneXmlEvent(String tag, String classname) {

 		if (this.miscProperties.containsKey("xmlevents")) {

-    		Class eventsClass;

-    		String xmleventsclass = this.miscProperties.getProperty("xmlevents");

+			Class eventsClass;

+			String xmleventsclass = this.miscProperties.getProperty("xmlevents");

 			try {

 				eventsClass = Class.forName(xmleventsclass);

 			} catch (ClassNotFoundException e) {

@@ -2629,7 +2713,7 @@
 				this.miscProperties.remove("xmlevents");

 				eventsClass = DefaultXmlEvents.class; //try setting the DefaultXmlEvents

 			}

-    		XmlEvents events = null;

+			XmlEvents events = null;

 			try {

 				events = (XmlEvents) eventsClass.newInstance();

 				events.addEvent(tag, classname); //call the custom events class

@@ -2641,13 +2725,13 @@
 				this.miscProperties.remove("xmlevents");

 				//continue to DefaultXmlEvents.addEvent below

 			}

-    	}

+		}

 		new DefaultXmlEvents().addEvent(tag, classname);

 	}

-    

-    Pattern xmleventClassPattern = Pattern.compile("" +

-    		"\\{class\\}(.*)");

-    protected String getXmlEventClassname(String value) {

+

+	Pattern xmleventClassPattern = Pattern.compile("" +

+			"\\{class\\}(.*)");

+	protected String getXmlEventClassname(String value) {

 		Matcher finder = xmleventClassPattern.matcher(value);

 		if (finder.find()) {

 			return finder.group(1);

@@ -2655,8 +2739,8 @@
 		throw new IllegalArgumentException(XMLEVENT_PROP_ERROR);

 	}

 

-    Pattern xmleventTagPattern = Pattern.compile("" +

-    		"\\{tag\\}([^}]+)\\{class\\}");

+	Pattern xmleventTagPattern = Pattern.compile("" +

+			"\\{tag\\}([^}]+)\\{class\\}");

 	protected String getXmlEventTag(String value) {

 		Matcher finder = xmleventTagPattern.matcher(value);

 		if (finder.find()) {

@@ -2666,79 +2750,79 @@
 	}

 

 	/**

-     * @param key

-     * @return true if the given key is the switch to turn on the 

-     * Hierarchy framework

-     */

-    protected boolean isHierarchySwitch(String key) {

-    	Matcher switchFinder = switchPattern.matcher(key);

-    	return switchFinder.find();

-    }

-    

-    /**

-     * determines if the given string represents an allowed 

-     * non converter property: (hierarchy builder, page history preserver,

-     * illegalname handler, autodetect spacekeys)

-     * @param input represents an entire converter/property string. For example:

-     * <br/>

-     * Wiki.0011.somefilename.propertytype=something

-     * @return true if it's an expected/allowed non converter property

-     */

-    public boolean isNonConverterProperty(String input) {

-    	String converterTypes = 

-    				"(" +

-    					"(" + 

-    						NONCONVERTERTYPE_HIERARCHYBUILDER + 

-    					")" + 

-    					"|" +

-    					"(" + 

-    						NONCONVERTERTYPE_PAGEHISTORYPRESERVATION + 

-    					")" +

-    					"|" +

-    					"(" + 

-							NONCONVERTERTYPE_ILLEGALHANDLING + 

+	 * @param key

+	 * @return true if the given key is the switch to turn on the 

+	 * Hierarchy framework

+	 */

+	protected boolean isHierarchySwitch(String key) {

+		Matcher switchFinder = switchPattern.matcher(key);

+		return switchFinder.find();

+	}

+

+	/**

+	 * determines if the given string represents an allowed 

+	 * non converter property: (hierarchy builder, page history preserver,

+	 * illegalname handler, autodetect spacekeys)

+	 * @param input represents an entire converter/property string. For example:

+	 * <br/>

+	 * Wiki.0011.somefilename.propertytype=something

+	 * @return true if it's an expected/allowed non converter property

+	 */

+	public boolean isNonConverterProperty(String input) {

+		String converterTypes = 

+				"(" +

+						"(" + 

+						NONCONVERTERTYPE_HIERARCHYBUILDER + 

+						")" + 

+						"|" +

+						"(" + 

+						NONCONVERTERTYPE_PAGEHISTORYPRESERVATION + 

 						")" +

-    					"|" +

-    					"(" + 

-							NONCONVERTERTYPE_AUTODETECTSPACEKEYS + 

+						"|" +

+						"(" + 

+						NONCONVERTERTYPE_ILLEGALHANDLING + 

+						")" +

+						"|" +

+						"(" + 

+						NONCONVERTERTYPE_AUTODETECTSPACEKEYS + 

 						")" +

 						"|" +

 						"(" +

-							NONCONVERTERTYPE_FILTERS + 

+						NONCONVERTERTYPE_FILTERS + 

 						")" +

 						"|" +

 						"(" +

-							NONCONVERTERTYPE_MISCPROPERTIES +

+						NONCONVERTERTYPE_MISCPROPERTIES +

 						")" +

 						"|" +

 						"(" +

-							NONCONVERTERTYPE_XMLEVENT +

+						NONCONVERTERTYPE_XMLEVENT +

 						")" +

-    				")";

-    	String converterPattern = "[-\\w\\d.]+?" + converterTypes + "=" + ".*";

-    	return input.matches(converterPattern);

-    }

-    /**

-     * @return true if the converter should handle page histories

-     */

-    public boolean isHandlingPageHistories() {

-    	return this.handlingPageHistories;

-    }

-    

-    /**

-     * @return true if the converter should handle page histories

-     */

-    public boolean isHandlingPageHistoriesFromFilename() {

-    	return this.handlingPageHistories && this.pageHistorySuffix != null;

-    }

-    

-    /**

-     * @return the current page history suffix

-     */

-    public String getPageHistorySuffix() {

-    	return this.pageHistorySuffix;

-    }

-    

+						")";

+		String converterPattern = "[-\\w\\d.]+?" + converterTypes + "=" + ".*";

+		return input.matches(converterPattern);

+	}

+	/**

+	 * @return true if the converter should handle page histories

+	 */

+	public boolean isHandlingPageHistories() {

+		return this.handlingPageHistories;

+	}

+

+	/**

+	 * @return true if the converter should handle page histories

+	 */

+	public boolean isHandlingPageHistoriesFromFilename() {

+		return this.handlingPageHistories && this.pageHistorySuffix != null;

+	}

+

+	/**

+	 * @return the current page history suffix

+	 */

+	public String getPageHistorySuffix() {

+		return this.pageHistorySuffix;

+	}

+

 	/**

 	 * sorts the given list of pages.

 	 * Note: sorting will take into account page name 

@@ -2748,10 +2832,16 @@
 	 */

 	protected List<Page> sortByHistory(List<Page> pages) {

 		this.state.updateNote("Sorting Pages by Page History");

+		log.debug("num of pages: " + pages.size());

 		List<Page> sortedPages = new ArrayList<Page>();

 		Set<Page> sorted = new TreeSet<Page>();

 		sorted.addAll(pages); //sort them and get rid of non-unique pages

 		sortedPages.addAll(sorted); //turn them back into a list

+		if (log.isDebugEnabled()) {

+			for (Page page : sorted) {

+				log.debug("Sorted pages: " + page.getFile().getName());

+			}

+		}

 		return sortedPages;

 	}

 

@@ -2792,7 +2882,7 @@
 	protected HierarchyHandler getHierarchyHandler() {

 		return hierarchyHandler;

 	}

-	

+

 	/**

 	 * sets how the hierarchy framework is to be used. 

 	 * @param input "UseBuilder", "UsePagenames", or "Default". 

@@ -2817,7 +2907,7 @@
 	public void resetFeedback() {

 		this.feedback = Feedback.NONE;

 	}

-	

+

 	/**

 	 * clears state relating to the error handling

 	 */

@@ -2825,7 +2915,7 @@
 		this.errors.clear();

 		this.hadConverterErrors = false;

 	}

-	

+

 	/**

 	 * clears state relating to the hierarchy framework

 	 */

@@ -2833,21 +2923,21 @@
 		this.hierarchyBuilder = null;

 		this.hierarchyHandler = HierarchyHandler.DEFAULT;

 	}

-	

+

 	/**

 	 * @return object contains information relating to errors triggered during the conversion

 	 */

 	public ConverterErrors getErrors() {

 		return this.errors;

 	}

-	

+

 	/**

 	 * @return true if the conversion has generated errors

 	 */

 	public boolean hadConverterErrors() {

 		return this.hadConverterErrors;

 	}

-	

+

 	/**

 	 * setter

 	 * @param running

@@ -2855,7 +2945,7 @@
 	protected void setRunning(boolean running) {

 		this.running = running; //used in junit

 	}

-	

+

 	/**

 	 * setter

 	 * @param settings

@@ -2864,7 +2954,7 @@
 		this.settings = settings; //used in junit

 	}

 

-	

+

 	/**

 	 * @return true if the illegal handling (names and links) should occur. 

 	 * false if it should be disabled

@@ -2872,22 +2962,22 @@
 	public boolean isIllegalHandlingEnabled() {

 		return illegalHandlingEnabled;

 	}

-	

+

 	public boolean isAutoDetectingSpacekeys() {

 		return autoDetectSpacekeys;

 	}

-	

+

 	public class AsciiVersionComparator implements Comparator {

-	      public int compare(Object a, Object b) {

-	    	  Page pa = (Page) a;

-	    	  Page pb = (Page) b;

-	    	  String sa = pa.getName().toLowerCase();

-	    	  String sb = pb.getName().toLowerCase();

-	    	  int ascii = sa.compareTo(sb);

-	    	  int sav = pa.getVersion();

-	    	  int sbv = pb.getVersion();

-	    	  int version = sbv - sav;

-	    	  return ascii - version;

-	      }

+		public int compare(Object a, Object b) {

+			Page pa = (Page) a;

+			Page pb = (Page) b;

+			String sa = pa.getName().toLowerCase();

+			String sb = pb.getName().toLowerCase();

+			int ascii = sa.compareTo(sb);

+			int sav = pa.getVersion();

+			int sbv = pb.getVersion();

+			int version = sbv - sav;

+			return ascii - version;

+		}

 	}

 }

diff --git a/src/com/atlassian/uwc/ui/Page.java b/src/com/atlassian/uwc/ui/Page.java
index 9a62b1d..b509a10 100644
--- a/src/com/atlassian/uwc/ui/Page.java
+++ b/src/com/atlassian/uwc/ui/Page.java
@@ -164,7 +164,14 @@
 		if (compareValue == 0) { 

 			if (!sortWithTimestamp) compareValue = versionA - versionB;

 		}

+		//if these are the same, double check file path in case the filename is the same

+		if (compareValue == 0) {

+			String pathA = this.path;

+			String pathB = b.getPath();

+			compareValue = pathA.compareTo(pathB);

+		}

 		

+		//NOTE: If we return 0, only one of these objects will win in a Set that is sorted with this method.

 		return compareValue;

 	}