Improved DokuwikiHierarchy uses space mapping and some other new settings, handle dokuwiki noformat syntax, discussion plugin to confluence comments,improved HierarchyLink handling to use automatic space mapping, some adjustments to table handling, bugfix to FilepathHierarchy which caused an NPE in linux env, Comment framework can specify whether to have the engine transform from markup or treat as xhtml
diff --git a/conf/converter.dokuwiki.properties b/conf/converter.dokuwiki.properties
index 7964f12..ed69ecd 100644
--- a/conf/converter.dokuwiki.properties
+++ b/conf/converter.dokuwiki.properties
@@ -25,6 +25,10 @@
 ## See DokuwikiHierarchy config notes in https://studio.plugins.atlassian.com/wiki/display/UWC/UWC+DokuWiki+Notes

 #DokuWiki.001.collision-titles-spacekey.property=Set,This

 #DokuWiki.001.collision-titles-foo.property=Bar

+## Tell the DokuwikiHierarchy if the default home page for a node is a sibling or child file of the node. Values can be 'sibling' or 'child'. Default is 'child'

+#DokuWiki.001.hierarchy-homepage-position.property=sibling

+## Tell the DokuwikiHierarchy what the filename of the homepage should be without the .txt. Values can be a string (like 'start'). Leave empty or commented to specify that it should be the same as the node name. 

+#DokuWiki.001.hierarchy-homepage-dokuwiki-filename.property=

 ## Tell the DokuwikiHierarchy which spacekeys are getting what dokuwiki directories

 ## space-[spacekey].property=dir,which,will,be,in,this,space

 ## The following would be saying that file system directory with name "dir" would be associated with 

@@ -43,6 +47,7 @@
 # Code (needs to be early so we can tokenize the contents)

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

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

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

 DokuWiki.0esc-lbrackets.java-regex=(?<!\[)\[(?!\[){replace-with}\\[

 DokuWiki.0esc-lcurlybrace.java-regex=(?<!\{)\{(?!\{){replace-with}\\{

 ## Tag Plugin - https://www.dokuwiki.org/plugin:tag

@@ -146,8 +151,10 @@
 ##   See: http://www.dokuwiki.org/plugin:folded

 ##   and: 

 #DokuWiki.61.folded.java-regex=(?s)[+]{4,4}([^|]+)\|(.*?)[+]{4,4}{replace-with}{expand:$1}$2{expand}

-## Discussion plugin removed

-#DokuWiki.62.discussion.java-regex=~~DISCUSSION[^~]*~~[\n]?{replace-with}

+## Discussions to Confluence comments

+## Note: requires meta-dir property be set. See DokuWiki.7.meta-dir.property

+DokuWiki.62.discussion-to-comments.class=com.atlassian.uwc.converters.dokuwiki.DiscussionConverter

+DokuWiki.62.remove-discussion.java-regex=~~DISCUSSION[^~]*~~[\n]?{replace-with}

 

 ## Remove double backslashes at ends of newlines

 #DokuWiki.65.doublebs.java-regex=(?m)\\\\${replace-with}

diff --git a/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion.comments b/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion.comments
new file mode 100644
index 0000000..9ed4528
--- /dev/null
+++ b/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion.comments
@@ -0,0 +1,7 @@
+a:5:{s:5:"title";N;s:6:"status";i:1;s:8:"comments";a:1:{s:32:"4df1fa69356ef92dd48fca5f004b3dc5";a:8:{s:4:"user";a:5:{s:2:"id";s:3:"foo";s:4:"name";s:15:"Foo Bar";s:4:"mail";s:15:"foo@bar.org";s:7:"address";s:0:"";s:3:"url";s:0:"";}s:4:"date";a:1:{s:7:"created";i:1333093999;}s:4:"show";b:1;s:3:"raw";s:65:"Testing 123";s:5:"xhtml";s:77:"
+<p>
+Testing 123
+
+</p>
+";s:6:"parent";N;s:7:"replies";a:0:{}s:3:"cid";s:32:"4df1fa69356ef92dd48fca5f004b3dc5";}}s:6:"number";i:1;s:11:"subscribers";N;}
+
diff --git a/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion_mult.comments b/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion_mult.comments
new file mode 100644
index 0000000..1491f6a
--- /dev/null
+++ b/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion_mult.comments
@@ -0,0 +1,12 @@
+a:5:{s:5:"title";N;s:6:"status";i:1;s:8:"comments";a:2:{s:32:"5efeab0c9a77feb0248e8d84029b345f";a:8:{s:4:"user";a:5:{s:2:"id";s:3:"foo";s:4:"name";s:11:"Foo Bar";s:4:"mail";s:22:"foo@bar.org.br";s:7:"address";s:0:"";s:3:"url";s:0:"";}s:4:"date";a:1:{s:7:"created";i:1253800808;}s:4:"show";b:1;s:3:"raw";s:110:"tralala";s:5:"xhtml";s:122:"
+<p>
+tralala
+
+</p>
+";s:6:"parent";N;s:7:"replies";a:0:{}s:3:"cid";s:32:"5efeab0c9a77feb0248e8d84029b345f";}s:32:"daa35c667fbb6b1c369a711aeb5adad6";a:8:{s:4:"user";a:5:{s:2:"id";s:3:"ella";s:4:"name";s:11:"Ella Fitzgerald";s:4:"mail";s:24:"ella@jazz.org";s:7:"address";s:0:"";s:3:"url";s:0:"";}s:4:"date";a:1:{s:7:"created";i:1254911413;}s:4:"show";b:1;s:3:"raw";s:122:"bedoobedoo whoa!";s:5:"xhtml";s:212:"
+<p>
+bedoobedoo <b>whoa!</b>
+
+</p>
+";s:6:"parent";N;s:7:"replies";a:0:{}s:3:"cid";s:32:"daa35c667fbb6b1c369a711aeb5adad6";}}s:6:"number";i:2;s:11:"subscribers";N;}
+
diff --git a/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion_none.comments b/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion_none.comments
new file mode 100644
index 0000000..175e626
--- /dev/null
+++ b/sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion_none.comments
@@ -0,0 +1,2 @@
+a:5:{s:5:"title";N;s:6:"status";i:1;s:8:"comments";a:0:{}s:6:"number";i:0;s:11:"subscribers";N;}
+
diff --git a/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion.txt b/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion.txt
new file mode 100644
index 0000000..2d74a4f
--- /dev/null
+++ b/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion.txt
@@ -0,0 +1,4 @@
+Removing Discussion plugin
+http://www.dokuwiki.org/plugin:discussion
+
+~~DISCUSSION~~
diff --git a/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion_mult.txt b/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion_mult.txt
new file mode 100644
index 0000000..2d74a4f
--- /dev/null
+++ b/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion_mult.txt
@@ -0,0 +1,4 @@
+Removing Discussion plugin
+http://www.dokuwiki.org/plugin:discussion
+
+~~DISCUSSION~~
diff --git a/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion_none.txt b/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion_none.txt
new file mode 100644
index 0000000..2d74a4f
--- /dev/null
+++ b/sampleData/dokuwiki/junit_resources/pages/SampleDokuwiki-InputDiscussion_none.txt
@@ -0,0 +1,4 @@
+Removing Discussion plugin
+http://www.dokuwiki.org/plugin:discussion
+
+~~DISCUSSION~~
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Drink/Juice/Apple.txt b/sampleData/hierarchy/dokuwiki-nodehome/Drink/Juice/Apple.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Drink/Juice/Apple.txt
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Drink/Water.txt b/sampleData/hierarchy/dokuwiki-nodehome/Drink/Water.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Drink/Water.txt
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Drink/juice.txt b/sampleData/hierarchy/dokuwiki-nodehome/Drink/juice.txt
new file mode 100644
index 0000000..233c2e6
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Drink/juice.txt
@@ -0,0 +1 @@
+This should be content for the Juice page
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Food/Baklava.txt b/sampleData/hierarchy/dokuwiki-nodehome/Food/Baklava.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Food/Baklava.txt
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Food/Fruit/Apple.txt b/sampleData/hierarchy/dokuwiki-nodehome/Food/Fruit/Apple.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Food/Fruit/Apple.txt
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/Apple.txt b/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/Apple.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/Apple.txt
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/Fruit/Apple.txt b/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/Fruit/Apple.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/Fruit/Apple.txt
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/fruit.txt b/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/fruit.txt
new file mode 100644
index 0000000..e254f7a
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Food/Pie/fruit.txt
@@ -0,0 +1 @@
+Pie ... Fruit ... Apple
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/Food/fruit.txt b/sampleData/hierarchy/dokuwiki-nodehome/Food/fruit.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/Food/fruit.txt
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/drink.txt b/sampleData/hierarchy/dokuwiki-nodehome/drink.txt
new file mode 100644
index 0000000..48ff20f
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/drink.txt
@@ -0,0 +1 @@
+This should be content for the Drink page
diff --git a/sampleData/hierarchy/dokuwiki-nodehome/food.txt b/sampleData/hierarchy/dokuwiki-nodehome/food.txt
new file mode 100644
index 0000000..a28c78e
--- /dev/null
+++ b/sampleData/hierarchy/dokuwiki-nodehome/food.txt
@@ -0,0 +1,27 @@
+This should be the top level start page.
+
+We're going to test out link and image namespace collision fixing.
+
+[[drink:start]]
+[[:drink:start]]
+[[drink:juice:start|Alias for the juice link]]
+[[drink:juice:apple]]
+[[drink:water]]
+[[food:start|Food]]
+[[food:Baklava]]
+[[food:fruit:start]]
+[[food:fruit:apple]]
+[[food:pie:start]]
+[[food:pie:apple]]
+[[food:pie:fruit:start]]
+[[food:pie:fruit:apple]]
+[[food:pie:start:apple]]
+[[otherspace:Testing 123]]
+
+{{drink:Wiki.png}} - render
+{{:drink:juice:Wiki.png}} - render
+{{drink:juice:test.pdf|}} - link to attachment
+{{food:pie:test.pdf|Alias?}}
+{{food:pie:fruit:Wiki.png}}
+{{otherspace:Wiki.png}}
+
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/DiscussionConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/DiscussionConverter.java
new file mode 100644
index 0000000..3faf51e
--- /dev/null
+++ b/src/com/atlassian/uwc/converters/dokuwiki/DiscussionConverter.java
@@ -0,0 +1,126 @@
+package com.atlassian.uwc.converters.dokuwiki;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import biz.artemis.util.FileUtils;
+
+import com.atlassian.uwc.converters.BaseConverter;
+import com.atlassian.uwc.converters.tikiwiki.RegexUtil;
+import com.atlassian.uwc.ui.Comment;
+import com.atlassian.uwc.ui.Page;
+
+public class DiscussionConverter extends DokuwikiUserDate {
+
+	@Override
+	public void convert(Page page) {
+		String input = page.getOriginalText();
+		if (hasDiscussion(input)) {
+			String commentData = getCommentData(page.getFile());
+			int numComments = getNumComments(commentData);
+			if (numComments > 0) {
+				Vector<String> comments = seperateComments(commentData);
+				for (String commentString : comments) {
+					Comment comment = parseComment(commentString);
+					page.addComment(comment);
+				}
+			}
+		}
+	}
+
+	Pattern discussionP = Pattern.compile("~~DISCUSSION[^~]*~~");
+	/**
+	 * 
+	 * @param input page content
+	 * @return true if the page indicates that there is a discussion
+	 */
+	protected boolean hasDiscussion(String input) {
+		Matcher discussionFinder = discussionP.matcher(input);
+		return discussionFinder.find();
+	}
+	
+	/**
+	 * @param pagefile
+	 * @return the contents of the comments file associated with the given page file
+	 */
+	protected String getCommentData(File pagefile) {
+		String metaFilename = getMetaFilename(pagefile.getPath(), ".comments");
+		File meta = new File(metaFilename);
+		try {
+			return FileUtils.readTextFile(meta);
+		} catch (IOException e) {
+			String message = "Problem reading meta file: " + metaFilename;
+			log.error(message, e);
+			addError(Feedback.BAD_FILE, message, false);
+			return "";
+		}
+	}
+
+	Pattern numCommentsP = Pattern.compile(";s:8:\"comments\";a:(\\d+):\\{");
+	/**
+	 * 
+	 * @param input contents of a comments file
+	 * @return number of comments in the file
+	 */
+	protected int getNumComments(String input) {
+		Matcher numFinder = numCommentsP.matcher(input);
+		if (numFinder.find()) {
+			String numS = numFinder.group(1);
+			return new Integer(numS);
+		}
+		return 0;
+	}
+
+	Pattern commentsStringP = Pattern.compile(";s:8:\"comments\";a:\\d+:\\{(.*?\\})s:6:\"number\"", Pattern.DOTALL);
+	Pattern eachComment = Pattern.compile("(.*?s:3:\"cid\";s:32:\"\\w{32,32}\";\\})", Pattern.DOTALL);
+	/**
+	 * @param input
+	 * @return each string in the vector contains all the data for an individual comment
+	 */
+	protected Vector<String> seperateComments(String input) {
+		Vector<String> all = new Vector<String>();
+		Matcher commentFinder = commentsStringP.matcher(input);
+		while (commentFinder.find()) {
+			String commentData = commentFinder.group(1);
+			Matcher eachFinder = eachComment.matcher(commentData);
+			while (eachFinder.find()) {
+				String each = eachFinder.group(1);
+				all.add(each);
+			}
+		}
+		
+		return all;
+	}
+
+	Pattern commentP = Pattern.compile("" +
+			"s:32:\"\\w{32,32}\";a:8:\\{s:4:\"user\";a:\\d:\\{s:2:\"id\";s:\\d+:\"([^\"]+)" + //username
+			".*?\\}s:4:\"date\\\";a:\\d:\\{.*?s:7:\"created\";i:(\\d+)" + //timestamp
+			".*?s:5:\"xhtml\";s:\\d+:\"(.*?)\";s:6:\"parent\";N;", //text 
+			Pattern.DOTALL);
+	protected Comment parseComment(String input) {
+		Matcher commentFinder = commentP.matcher(input);
+		while (commentFinder.find()) {
+			String creator = commentFinder.group(1);
+			String timestamp = commentFinder.group(2);
+			String text = commentFinder.group(3);
+			timestamp = formatTimestamp(timestamp);
+			boolean isXhtml = true;
+			return new Comment(text, creator, timestamp, isXhtml);
+		}
+		return null;
+	}
+
+	private String formatTimestamp(String timestamp) {
+		long timestring = Long.parseLong(timestamp);
+		Date date = new Date(timestring*1000);
+		DateFormat dateFormat = new SimpleDateFormat("yyyy:MM:dd:HH:mm:ss:SS");
+		return dateFormat.format(date);
+	}
+
+}
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/DiscussionConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/DiscussionConverterTest.java
new file mode 100644
index 0000000..61adc34
--- /dev/null
+++ b/src/com/atlassian/uwc/converters/dokuwiki/DiscussionConverterTest.java
@@ -0,0 +1,198 @@
+package com.atlassian.uwc.converters.dokuwiki;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Vector;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+
+import com.atlassian.uwc.ui.Comment;
+import com.atlassian.uwc.ui.FileUtils;
+import com.atlassian.uwc.ui.Page;
+
+public class DiscussionConverterTest extends TestCase {
+
+	private static final String METADIR = "sampleData/dokuwiki/junit_resources/meta";
+	private static final String SAMPLEMETA = METADIR + "/SampleDokuwiki-InputDiscussion.comments";
+	private static final String PAGESDIR = "sampleData/dokuwiki/junit_resources/pages";
+	private static final String SAMPLEPAGE = PAGESDIR+"/SampleDokuwiki-InputDiscussion.txt";
+	DiscussionConverter tester = null;
+	Logger log = Logger.getLogger(this.getClass());
+	protected void setUp() throws Exception {
+		tester = new DiscussionConverter();
+		PropertyConfigurator.configure("log4j.properties");
+		tester.getProperties().setProperty("meta-dir", METADIR);
+		tester.getProperties().setProperty("filepath-hierarchy-ignorable-ancestors", PAGESDIR);
+		super.setUp();
+	}
+
+
+	public void testHasDiscussion() {
+		String input;
+		boolean expected, actual;
+		input = "foobar\n" +
+				"~~DISCUSSION~~\n";
+		expected = true;
+		actual = tester.hasDiscussion(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		input = "testing\n";
+		expected = false;
+		actual = tester.hasDiscussion(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+
+	public void testGetMetaFilepath() {
+		Page page = new Page(new File(SAMPLEPAGE));
+		String expected = "sampleData/dokuwiki/junit_resources/meta/SampleDokuwiki-InputDiscussion.comments";
+		String actual = tester.getMetaFilename(page.getFile().getPath(), ".comments");
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	public void testGetCommentData() {
+		Page page = new Page(new File(SAMPLEPAGE));
+		String actual = tester.getCommentData(page.getFile());
+		assertNotNull(actual);
+		assertTrue(actual.contains("Foo Bar"));
+		assertTrue(actual.contains("foo"));
+		assertTrue(actual.contains("Testing 123"));
+	}
+
+	public void testGetNumComments() throws IOException {
+		String input = FileUtils.readTextFile(new File(SAMPLEMETA));
+		int expected = 1;
+		int actual = tester.getNumComments(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		input = FileUtils.readTextFile(new File(METADIR+"/SampleDokuwiki-InputDiscussion_mult.comments"));
+		expected = 2;
+		actual = tester.getNumComments(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		input = FileUtils.readTextFile(new File(METADIR+"/SampleDokuwiki-InputDiscussion_none.comments"));
+		expected = 0;
+		actual = tester.getNumComments(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		input = "a:2:{s:5:\"title\";N;s:6:\"status\";i:1;}\n";
+		expected = 0;
+		actual = tester.getNumComments(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+	}
+
+	public void testSeperateComments() throws IOException {
+		String input, expected, actual;
+		input = FileUtils.readTextFile(new File(SAMPLEMETA));
+		Vector<String> all = tester.seperateComments(input);
+		assertNotNull(all);
+		assertEquals(1, all.size());
+		expected = "s:32:\"4df1fa69356ef92dd48fca5f004b3dc5\";a:8:{s:4:\"user\";a:5:{s:2:\"id\";s:3:\"foo\";s:4:\"name\";s:15:\"Foo Bar\";s:4:\"mail\";s:15:\"foo@bar.org\";s:7:\"address\";s:0:\"\";s:3:\"url\";s:0:\"\";}s:4:\"date\";a:1:{s:7:\"created\";i:1333093999;}s:4:\"show\";b:1;s:3:\"raw\";s:65:\"Testing 123\";s:5:\"xhtml\";s:77:\"\n" + 
+				"<p>\n" + 
+				"Testing 123\n" + 
+				"\n" + 
+				"</p>\n" + 
+				"\";s:6:\"parent\";N;s:7:\"replies\";a:0:{}s:3:\"cid\";s:32:\"4df1fa69356ef92dd48fca5f004b3dc5\";}";
+		actual = all.get(0);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		all.clear();
+		input = FileUtils.readTextFile(new File(METADIR+"/SampleDokuwiki-InputDiscussion_mult.comments"));
+		all = tester.seperateComments(input);
+		assertNotNull(all);
+		assertEquals(2, all.size());
+		expected = "s:32:\"5efeab0c9a77feb0248e8d84029b345f\";a:8:{s:4:\"user\";a:5:{s:2:\"id\";s:3:\"foo\";s:4:\"name\";s:11:\"Foo Bar\";s:4:\"mail\";s:22:\"foo@bar.org.br\";s:7:\"address\";s:0:\"\";s:3:\"url\";s:0:\"\";}s:4:\"date\";a:1:{s:7:\"created\";i:1253800808;}s:4:\"show\";b:1;s:3:\"raw\";s:110:\"tralala\";s:5:\"xhtml\";s:122:\"\n" + 
+				"<p>\n" + 
+				"tralala\n" + 
+				"\n" + 
+				"</p>\n" + 
+				"\";s:6:\"parent\";N;s:7:\"replies\";a:0:{}s:3:\"cid\";s:32:\"5efeab0c9a77feb0248e8d84029b345f\";}";
+		actual = all.get(0);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		expected = "s:32:\"daa35c667fbb6b1c369a711aeb5adad6\";a:8:{s:4:\"user\";a:5:{s:2:\"id\";s:3:\"ella\";s:4:\"name\";s:11:\"Ella Fitzgerald\";s:4:\"mail\";s:24:\"ella@jazz.org\";s:7:\"address\";s:0:\"\";s:3:\"url\";s:0:\"\";}s:4:\"date\";a:1:{s:7:\"created\";i:1254911413;}s:4:\"show\";b:1;s:3:\"raw\";s:122:\"bedoobedoo whoa!\";s:5:\"xhtml\";s:212:\"\n" + 
+				"<p>\n" + 
+				"bedoobedoo <b>whoa!</b>\n" + 
+				"\n" + 
+				"</p>\n" + 
+				"\";s:6:\"parent\";N;s:7:\"replies\";a:0:{}s:3:\"cid\";s:32:\"daa35c667fbb6b1c369a711aeb5adad6\";}";
+		actual = all.get(1);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+
+	public void testParseComment() {
+		String input, expected;
+		String xhtml = "<p>\n" + 
+						"bedoobedoo <b>whoa!</b>\n" + 
+						"\n" + 
+						"</p>\n";
+		input = "s:32:\"daa35c667fbb6b1c369a711aeb5adad6\";a:8:{s:4:\"user\";a:5:{s:2:\"id\";s:3:\"ella\";s:4:\"name\";s:11:\"Ella Fitzgerald\";s:4:\"mail\";s:24:\"ella@jazz.org\";s:7:\"address\";s:0:\"\";s:3:\"url\";s:0:\"\";}s:4:\"date\";a:1:{s:7:\"created\";i:1254911413;}s:4:\"show\";b:1;s:3:\"raw\";s:122:\"bedoobedoo whoa!\";s:5:\"xhtml\";s:212:\"\n" + xhtml + "\";s:6:\"parent\";N;s:7:\"replies\";a:0:{}s:3:\"cid\";s:32:\"daa35c667fbb6b1c369a711aeb5adad6\";}";
+		
+		Comment actual = tester.parseComment(input);
+		
+		expected = "ella";
+		assertNotNull(actual);
+		assertEquals(expected, actual.creator);
+		
+		expected = "\n"+xhtml;
+		assertNotNull(actual);
+		assertEquals(expected, actual.text);
+		
+		expected = "2009:10:07:06:30:13:00";
+		assertNotNull(actual);
+		assertEquals(expected, actual.timestamp);
+		
+		assertTrue(actual.isXhtml());
+	}
+
+	public void testConvert_One() throws IOException {
+		File file = new File(SAMPLEPAGE);
+		Page page = new Page(file);
+		page.setOriginalText(FileUtils.readTextFile(file));
+		tester.convert(page);
+		Vector<Comment> actual = page.getAllCommentData();
+		assertNotNull(actual);
+		assertEquals(1, actual.size());
+		assertEquals("foo", actual.get(0).creator);
+		assertTrue(actual.get(0).text.contains("<p>\nTesting 123"));
+	}
+	
+	public void testConvert_Mult() throws IOException {
+		String path = PAGESDIR+"/SampleDokuwiki-InputDiscussion_mult.txt";
+		File file = new File(path);
+		Page page = new Page(file);
+		page.setOriginalText(FileUtils.readTextFile(file));
+		tester.convert(page);
+		
+		Vector<Comment> actual = page.getAllCommentData();
+		assertNotNull(actual);
+		assertEquals(2, actual.size());
+		assertEquals("foo", actual.get(0).creator);
+		assertTrue(actual.get(0).text.contains("tralala"));
+		assertEquals("ella", actual.get(1).creator);
+		assertTrue(actual.get(1).text.contains("<b>whoa!</b>"));
+	}
+	
+	public void testConvert_None() throws IOException {
+		String path = PAGESDIR+"/SampleDokuwiki-InputDiscussion_none.txt";
+		File file = new File(path);
+		Page page = new Page(file);
+		page.setOriginalText(FileUtils.readTextFile(file));
+		tester.convert(page);
+		
+		assertNotNull(page.getAllCommentData());
+		assertEquals(0, page.getAllCommentData().size());
+	}
+}
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
index e75dc1e..d541602 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
@@ -48,6 +48,9 @@
 	}
 
 	private String createChangeFilename(String path) {
+		return getMetaFilename(path, ".changes");
+	}
+	protected String getMetaFilename(String path, String filetype) {
 		String metadir = getProperties().getProperty("meta-dir", null);
 		if (metadir == null) {
 			log.warn("Could not handle user and date data. meta-dir property must be set. Skipping");
@@ -59,7 +62,7 @@
 			return null;
 		}
 		String relative = path.replaceFirst("\\Q" + ignorable + "\\E", "");
-		relative = relative.replaceFirst("\\.txt$", ".changes");
+		relative = relative.replaceFirst("\\.txt$", filetype);
 		if (relative.startsWith(File.separator) && metadir.endsWith(File.separator))
 			relative = relative.substring(1);
 		if (!relative.startsWith(File.separator) && !metadir.endsWith(File.separator))
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
index 1a9f7bd..f798df9 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
@@ -13,19 +13,26 @@
 
 	public void convert(Page page) {
 		String input = page.getOriginalText();
-		String converted = convertLink(input, getCurrentPath(page));
+		String converted = convertLink(input, getCurrentPath(page), getSpacekey(page));
 		page.setConvertedText(converted);
 
 	}
+	
+	public String getSpacekey(Page page) {
+		if (page != null && page.getSpacekey() != null) {
+			return page.getSpacekey();
+		}
+		return getProperties().getProperty("spacekey", null);
+	}
 
 	Pattern link = Pattern.compile("(?<=\\[)\\[([^\\]]*)\\](?=\\])");
 	Pattern onecolon = Pattern.compile("^([^:]*:)([^:]*)$");
 	protected String convertLink(String input) {
-		return convertLink(input, null);
+		return convertLink(input, null, getSpacekey(null));
 	}
-	protected String convertLink(String input, String currentPath) {
+	protected String convertLink(String input, String currentPath, String spacekey) {
 		Matcher linkFinder = link.matcher(input);
-		String currentSpacekey = getProperties().getProperty("spacekey", null);
+		String currentSpacekey = spacekey;
 		Vector<String> allspaces = getSpaces();
 		HashMap<String,String> namespaces = getDokuDirectories();
 		StringBuffer sb = new StringBuffer();
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
index 5973a27..8df5ef7 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
@@ -7,6 +7,8 @@
 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;
 
+import com.atlassian.uwc.ui.Page;
+
 public class HierarchyLinkConverterTest extends TestCase {
 
 	HierarchyLinkConverter tester = null;
@@ -85,7 +87,8 @@
 				"[Alias|food:Drink]\n" + 
 				"[Fruit Drink|food:Drink Fruit]\n" +
 				"[food:Cranberry]";
-		actual = tester.convertLink(input, "drink/");
+		String spacekey = "food";
+		actual = tester.convertLink(input, "drink/", spacekey);
 		assertNotNull(actual);
 		assertEquals(expected, actual);
 	}
@@ -98,4 +101,18 @@
 		assertNotNull(actual);
 		assertEquals(expected, actual); 
 	}
+	
+	public void testConvertWithPageByPageSpaces() {
+		Page page = new Page(null);
+		page.setOriginalText("[[.:home]]\n" +
+				"[[drink:start]]\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
+				"[food:Drink]\n"; //this one uses the mapping (drink points to food)
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
 }
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverter.java
index 3fbbae3..f0f339b 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverter.java
@@ -20,6 +20,7 @@
 
 	Pattern colspan = Pattern.compile("[|]{2,}");
 	protected String prep(String input) {
+		input = removeEmptyColspanLines(input);
 		Matcher spanFinder = colspan.matcher(input);
 		StringBuffer sb = new StringBuffer();
 		boolean found = false;
@@ -37,6 +38,23 @@
 		}
 		return input;
 	}
+	Pattern emptycolspan = Pattern.compile("(\n{2,})\\|+(?=\n)");
+	private String removeEmptyColspanLines(String input) {
+		Matcher emptyFinder = emptycolspan.matcher(input);
+		StringBuffer sb = new StringBuffer();
+		boolean found = false;
+		while (emptyFinder.find()) {
+			found = true;
+			String replacement = emptyFinder.group(1);
+			replacement = RegexUtil.handleEscapesInReplacement(replacement);
+			emptyFinder.appendReplacement(sb, replacement);
+		}
+		if (found) {
+			emptyFinder.appendTail(sb);
+			return sb.toString();
+		}
+		return input;
+	}
 
 
 }
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverterTest.java
index 12d6f2f..e8af0bc 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverterTest.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/PrepColSpansConverterTest.java
@@ -27,7 +27,7 @@
 		assertEquals(expected, actual);
 	}
 	
-	public void testPropComplicated() {
+	public void testPrepComplicated() {
 		String input, expected, actual;
 		input = "^ Heading 1      ^ Heading 2       ^ Heading 3          ^\n" + 
 				"| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |\n" + 
@@ -58,4 +58,46 @@
 		assertEquals(expected, actual);
 	}
 
+	public void testPrepExtraColspanRow() {
+		String input, expected, actual;
+		input = "^ H1  ^ H2 ^ H3 ^ H4 ^ H5 ^ H6 ^\n" + 
+				"^ rowspan starts      |  r1c1 | r1c2 | r1c3  | r1c4  | r1c5 |\n" + 
+				"^ :::          |                           |                               |                                |                                | tada  |\n" + 
+				"^ :::          |                           |                               |                                |                                |  foo |\n" + 
+				"|              |                           |                               |                                |                                | bar  |\n" + 
+				"| last row | tada |                               |                                |                                |                                                 |\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"|||||\n" + 
+				"\n" + 
+				"^ H1-2 ^ H2-2^\n" + 
+				"^ Simple table | http://backoffice.do.dev.euc  |\n"; 
+		expected = "^ H1  ^ H2 ^ H3 ^ H4 ^ H5 ^ H6 ^\n" + 
+				"^ rowspan starts      |  r1c1 | r1c2 | r1c3  | r1c4  | r1c5 |\n" + 
+				"^ :::          |                           |                               |                                |                                | tada  |\n" + 
+				"^ :::          |                           |                               |                                |                                |  foo |\n" + 
+				"|              |                           |                               |                                |                                | bar  |\n" + 
+				"| last row | tada |                               |                                |                                |                                                 |\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"\n" + 
+				"^ H1-2 ^ H2-2^\n" + 
+				"^ Simple table | http://backoffice.do.dev.euc  |\n"; 
+		actual = tester.prep(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
 }
diff --git a/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchy.java b/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchy.java
index 745b5e4..e0b37da 100644
--- a/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchy.java
+++ b/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchy.java
@@ -20,13 +20,14 @@
 		//run the filepath hierarchy first -
 		HierarchyNode node = super.buildHierarchy(pages);
 		//move spacekeys
-		node = handleSpacekeyBranch(node);
+		node = handleSpacekeyBranchWithProp(node);
 		//handle start pages
-		node = handleStartPages(node, true);
-		String spacekey = getProperties().getProperty("spacekey", "");
-		Vector<String> candidates = getCollisionsCandidates(spacekey);
+		node = handleHomePages(node, true);
+		//handle spacekeys not with a property, but with page.getSpacekey setting
+		node = handleSpacekeyBranchWithPage(node);
+		
 		//fix collisions
-		node = fixCollisions(node, candidates);
+		node = fixCollisions(node);
 		//fix titles
 		node = fixTitles(node);
 		//attach images
@@ -37,7 +38,7 @@
 		return node;
 	}
 
-	private HierarchyNode handleSpacekeyBranch(HierarchyNode node) {
+	private HierarchyNode handleSpacekeyBranchWithProp(HierarchyNode node) {
 		String spacekey = getProperties().getProperty("spacekey");
 		if (spacekey != null && !"".equals(spacekey)) { 
 			Set<HierarchyNode> top = node.getChildren();
@@ -45,12 +46,7 @@
 				HierarchyNode topnode = (HierarchyNode) iter.next();
 				if (topnode.getName().toLowerCase().equals(spacekey.toLowerCase())) {
 					//topnode children should be top level
-					Set<HierarchyNode> children = topnode.getChildren();
-					iter.remove(); //Only allowed way to remove from an iterator. 
-					topnode.setParent(null); //since we have to use iter.remove instead of node.removeChild(topnode)
-					for (HierarchyNode child : children) {
-						node.addChild(child);
-					}
+					setTopNodeBranch(node, iter, topnode);
 					break;
 				}
 			}
@@ -58,12 +54,85 @@
 		return node;
 	}
 
-	private HierarchyNode handleStartPages(HierarchyNode node, boolean top) {
+	private HierarchyNode handleSpacekeyBranchWithPage(HierarchyNode root) {
+		Set<HierarchyNode> top = root.getChildren();
+		Vector<HierarchyNode> ordered = new Vector<HierarchyNode>(top);
+		for (HierarchyNode topnode : ordered) {
+			root = handleSpacekeyBranchTop(root, topnode);
+		}
+		return root;
+	}
+
+	private HierarchyNode handleSpacekeyBranchTop(HierarchyNode root, HierarchyNode node) {
+		if (node.getPage() == null) {
+			//FIXME Do we want to do anything here? or just go straight to children 
+		}
+		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 (spacekey.equals(topnode.getPage().getSpacekey())) {
+						Set<HierarchyNode> children = topnode.getChildren();
+						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();
+		Vector<HierarchyNode> ordered = new Vector<HierarchyNode>(nextSet);
+		for (HierarchyNode next : ordered) {
+			root = handleSpacekeyBranchTop(root, next);
+		}
+		return root;
+	}
+
+	private HierarchyNode setAncestorsBySpacekey(HierarchyNode root, HierarchyNode node,
+			String spacekey) {
+		HierarchyNode parent = node.getParent();
+		if (parent.getName() == null) { //we're at the root level return
+			return node;
+		}
+		if (parent.getPage() == null) {
+			Page page = createPage(parent.getName());
+			page.setSpacekey(spacekey);
+			parent.setPage(page);
+			parent = setAncestorsBySpacekey(root, parent, spacekey);
+			if (parent.getParent() == null)
+				root.addChild(parent);
+		}
+		else if (parent.getPage().getSpacekey() == null) {
+			parent.getPage().setSpacekey(spacekey);
+			parent = setAncestorsBySpacekey(root, parent, spacekey);
+		}
+		else if (!parent.getPage().getSpacekey().equals(spacekey)) {
+			parent.removeChild(node);
+		}
+		return node;
+	}
+
+	private void setTopNodeBranch(HierarchyNode root, Iterator topiter, HierarchyNode nexttopnode) {
+		Set<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) {
+			root.addChild(child);
+		}
+	}
+
+	private HierarchyNode handleHomePages(HierarchyNode node, boolean top) {
 		Set<HierarchyNode> children = node.getChildren();
 		for (Iterator iter = children.iterator(); iter.hasNext();) { 
 			HierarchyNode child = (HierarchyNode) iter.next();
 			String name = child.getName();
-			if (name.toLowerCase().equals("start") && !top) {
+			String dokuwikiFilename = getDokuwikiHomepageFilename();
+			if (name.toLowerCase().equals(dokuwikiFilename) && !top) {
 				Page page = child.getPage();
 				if (page == null) continue; //mid level start directories
 				iter.remove(); //only allowed way to remove from an iterator
@@ -73,16 +142,22 @@
 				log.debug("Moving start page to parent. Changing start page name: " + node.getName());
 				node.getPage().setName(node.getName());
 			}
-			child = handleStartPages(child, false);
+			child = handleHomePages(child, false);
 		}
 		return node;
 	}
 
-	private HierarchyNode fixCollisions(HierarchyNode node, Vector<String> collisions) {
-		if (collisions.isEmpty()) return node;
+	public String getDokuwikiHomepageFilename() {
+		return getProperties().getProperty("hierarchy-homepage-dokuwiki-filename", "");
+	}
+
+	private HierarchyNode fixCollisions(HierarchyNode node) {
+		
 		Set<HierarchyNode> children = node.getChildren();
 		for (Iterator iter = children.iterator(); iter.hasNext();) { 
 			HierarchyNode child = (HierarchyNode) iter.next();
+			
+			Vector<String> collisions = getCollisionsForThisNode(child);
 			for (String name : collisions) {
 				String eqname = equalize(name);
 				String childname = equalize(child.getName());
@@ -97,18 +172,29 @@
 					if (child.getPage() != null) child.getPage().setName(newname);
 				}
 			}
-			child = fixCollisions(child, collisions);
+			child = fixCollisions(child);
 		}
 		return node;
 	}
 
+	public Vector<String> getCollisionsForThisNode(HierarchyNode node) {
+		log.debug("node.getPage: " + node.getPage());
+		if (node.getPage() != null) log.debug("node.getPage.getSpacekey: " + node.getPage().getSpacekey());
+		String spacekey = (node.getPage() != null && node.getPage().getSpacekey() != null)?
+				node.getPage().getSpacekey() :
+				getProperties().getProperty("spacekey", "");
+		return getCollisionsCandidates(spacekey);
+	}
+
 	protected Vector<String> getCollisionsCandidates(String spacekey) {
 		Properties props = getProperties();
 		Vector<String> candidates = new Vector<String>();
+		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);
 				if ("".equals(namesraw)) continue;
 				String[] names = namesraw.split(",");
 				for (String name : names) {
@@ -117,6 +203,7 @@
 				}
 			}
 		}
+		log.debug("candidates size? " + candidates.size());
 		return candidates;
 	}
 
@@ -218,4 +305,64 @@
 		}
 		return node;
 	}
+	
+	
+	Page currentpage;
+	HierarchyNode currentParent;
+	boolean combineHomepageNodes = false;
+	@Override 
+	protected void buildRelationships(Page page, HierarchyNode root) {
+		currentpage = page;
+		combineHomepageNodes = false;
+		currentParent = null;
+		//DELETE
+		if (page.getFile().getPath().endsWith("forumdescription.txt")) {
+			int food = 0;
+		}
+		super.buildRelationships(page, root);
+		if (combineHomepageNodes) {
+			combineHomepages(page);
+		}
+	}
+
+	public void combineHomepages(Page page) {
+		Set<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;
+			}
+		}
+		if (first.getPage() == null) {
+			combineHomepages(first, second, page);
+		}
+		else { 
+			combineHomepages(second, first, page);
+		}
+	}
+	
+	private void combineHomepages(HierarchyNode nullPageNode, HierarchyNode noChildrenNode,
+			Page page) {
+		//this one represents the one with all the hierarchy data
+		nullPageNode.setPage(page); 
+		//this one represents the one that (used) to have page data. We don't need it anymore
+		noChildrenNode.getParent().removeChild(noChildrenNode); 
+	}
+
+	@Override
+	protected boolean hasExistingRelationship(HierarchyNode parent, String childname) {
+		boolean hasRel = super.hasExistingRelationship(parent, childname);
+		if (hasRel && "".equals(getProperties().getProperty("hierarchy-homepage-dokuwiki-filename"))) {
+			if (parent.getPage() == null && currentpage.getName().equalsIgnoreCase(childname)) {
+				combineHomepageNodes = true;
+				currentParent = parent;
+				return false;
+			}
+			return hasRel;
+		}
+		return hasRel;
+			
+	}
 }
diff --git a/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchyTest.java b/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchyTest.java
index c73530b..89565ba 100644
--- a/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchyTest.java
+++ b/src/com/atlassian/uwc/hierarchies/DokuwikiHierarchyTest.java
@@ -24,11 +24,15 @@
 	protected void setUp() throws Exception {
 		tester = new DokuwikiHierarchy();
 		PropertyConfigurator.configure("log4j.properties");
-		
+		Properties props = tester.getProperties();
+		//changing some default handling, so need to set these now
+		props.put("hierarchy-homepage-position", "child"); //default is child
+		props.put("hierarchy-homepage-dokuwiki-filename", "start"); //default is empty. means the nodename
+		tester.setProperties(props);
 	}
 
 	public void testBuildHierarchy() {
-		Properties props = new Properties();
+		Properties props = tester.getProperties();
 		props.setProperty("spacekey", "food");
 		props.setProperty("collision-titles-food", "Apple");
 		props.put("filepath-hierarchy-ext", "");
@@ -91,20 +95,22 @@
 		for (int i = 0; i < exp.length; i++) {
 			String expected = exp[i];
 			boolean found = false;
+			String msg = "";
 			for (Iterator iter = nodes.iterator(); iter.hasNext();) {
 				HierarchyNode node = (HierarchyNode) iter.next();
 				String actual = node.getName();
+				msg = "act: " + actual + ", exp: " + expected;
 				if (expected.equals(actual)) {
 					found = true; 
 					break;
 				}
 			}
-			assertTrue(found);
+			assertTrue(msg, found);
 		}
 	}
 	
 	public void testBuildHierarchy_midlevelstartpages() {
-		Properties props = new Properties();
+		Properties props = tester.getProperties();
 		props.setProperty("spacekey", "food");
 		props.setProperty("collision-titles-food", "Apple");
 		props.put("filepath-hierarchy-ext", "");
@@ -150,7 +156,7 @@
 	
 	
 	public void testBuildHierarchy_collision_levelprop() {
-		Properties props = new Properties();
+		Properties props = tester.getProperties();
 		props.setProperty("spacekey", "food");
 		props.setProperty("collision-titles-food", "Apple,Fruit");
 		props.put("filepath-hierarchy-ext", "");
@@ -207,9 +213,86 @@
 
 	}
 	
+	public void testBuildHierarchy_multspaces() {
+		Properties props = tester.getProperties();
+		props.put("filepath-hierarchy-ext", "");
+		props.put("filepath-hierarchy-ignorable-ancestors", "sampleData/hierarchy/dokuwiki");
+		props.setProperty("collision-titles-pie", "Apple");
+		tester.setProperties(props);
+		
+		File sampledir = new File("sampleData/hierarchy/dokuwiki");
+		Collection<Page> pages = new Vector<Page>();
+		assertTrue(sampledir.exists());
+		File[] files = sampledir.listFiles(new NoSvnFilter());
+		pages = createPages(pages, files);
+		//set some spacekeys (as if SpaceConverter had set this)
+		for (Page page : pages) {
+			if (page.getFile().getPath().matches(".*?Food\\/Pie\\/.*")) {
+				page.setSpacekey("pie");
+			}
+			else if (page.getFile().getPath().matches(".*?Drink\\/.*")) {
+				page.setSpacekey("drink");
+			}
+			else {
+				page.setSpacekey("food");
+			}
+		}
+		
+		HierarchyNode root = tester.buildHierarchy(pages);
+		assertNotNull(root); //root node
+		assertNull(root.getName());
+		assertNull(root.getPage());
+		assertNull(root.getParent());
+		assertNotNull(root.getChildren());
+		
+		Set<HierarchyNode> top = root.getChildren();
+		assertEquals(3, top.size());
+		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
+		nodes0.addAll(top);
+		String[] exp = {"Drink", "Food", "Pie"};
+		testNodeResults(nodes0, exp);
+		
+		//needs more than one level of parent to avoid collision
+		HierarchyNode drink1 = getNode("Drink", nodes0);
+		assertNotNull(drink1);
+		assertNotNull(drink1.getPage());
+		assertEquals(2, drink1.getChildren().size());
+		Vector<HierarchyNode> fruitnodes1 = new Vector<HierarchyNode>();
+		fruitnodes1.addAll(drink1.getChildren());
+		String[] exp2 = {"Juice", "Water"};
+		testNodeResults(fruitnodes1, exp2);
+
+		HierarchyNode pie = getNode("Pie", nodes0);
+		assertNotNull(pie);
+		assertEquals(3, pie.getChildren().size());
+		Vector<HierarchyNode> pienodes = new Vector<HierarchyNode>();
+		pienodes.addAll(pie.getChildren());
+		String[] exppie = {"Pie Apple", "Fruit", "Start"};
+		testNodeResults(pienodes, exppie);
+		
+		HierarchyNode piefruit = getNode("Fruit", pienodes);
+		assertNotNull(piefruit);
+		assertNotNull(piefruit.getPage());
+		assertEquals(1, piefruit.getChildren().size());
+		Vector<HierarchyNode> fruitnodes2 = new Vector<HierarchyNode>();
+		fruitnodes2.addAll(piefruit.getChildren());
+		String[] expfruit = {"Fruit Apple"};
+		testNodeResults(fruitnodes2, expfruit);
+		
+		HierarchyNode food = getNode("Food", nodes0);
+		assertNotNull(food);
+		assertEquals(2, food.getChildren().size());
+		Vector<HierarchyNode> foodnodes = new Vector<HierarchyNode>();
+		foodnodes.addAll(food.getChildren());
+		String[] expfood = {"Baklava", "Fruit"};
+		testNodeResults(foodnodes, expfood);
+	}
+	
+	
+	
 	public void testBuildHierarchy_attachimages() {
 		
-		Properties props = new Properties();
+		Properties props = tester.getProperties();
 		props.setProperty("spacekey", "image");
 		props.setProperty("space-image", "images,wiki,test,playground");
 		props.put("filepath-hierarchy-ext", "");
@@ -284,7 +367,7 @@
 	}
 	
 	public void testBuildHierarchy_fixBranchNames() {
-		Properties props = new Properties();
+		Properties props = tester.getProperties();
 		props.setProperty("spacekey", "food");
 		props.setProperty("collision-titles-food", "Apple");
 		props.put("filepath-hierarchy-ext", "");
@@ -340,6 +423,164 @@
 		assertEquals("Juice Apple", juicenodes.get(0).getName());
 	}
 	
+	public void testBuildHierarchy_startpropfalse() {
+		Properties props = tester.getProperties();
+		props.setProperty("collision-titles-food", "Apple,Fruit");
+		props.put("filepath-hierarchy-ext", "");
+		String samplepath = "sampleData/hierarchy/dokuwiki-nodehome"; 
+		props.put("filepath-hierarchy-ignorable-ancestors", samplepath);
+		//set a property to identify the position of the homepage file
+		props.put("hierarchy-homepage-position", "sibling"); //default is child
+		//set a property to identify the homepage file 
+		props.put("hierarchy-homepage-dokuwiki-filename", ""); //default is empty. means the nodename
+		tester.setProperties(props);
+		
+		File sampledir = new File(samplepath);
+		Collection<Page> pages = new Vector<Page>();
+		assertTrue(sampledir.exists());
+		File[] files = sampledir.listFiles(new NoSvnFilter());
+		pages = createPages(pages, files);
+
+		HierarchyNode root = tester.buildHierarchy(pages);
+		assertNotNull(root); //root node
+		assertNull(root.getName());
+		assertNull(root.getPage());
+		assertNull(root.getParent());
+		assertNotNull(root.getChildren());
+		
+		Set<HierarchyNode> top = root.getChildren();
+		assertEquals(2, top.size());
+		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
+		nodes0.addAll(top);
+		String[] exp = {"Drink", "Food" };
+		testNodeResults(nodes0, exp);
+		
+		//needs more than one level of parent to avoid collision
+		HierarchyNode drink = getNode("Drink", nodes0);
+		assertNotNull(drink);
+		assertNotNull(drink.getPage());
+		assertEquals(2, drink.getChildren().size());
+		Vector<HierarchyNode> drinknodes1 = new Vector<HierarchyNode>();
+		drinknodes1.addAll(drink.getChildren());
+		String[] exp2 = {"Juice", "Water"};
+		testNodeResults(drinknodes1, exp2);
+		
+		HierarchyNode juice = getNode("Juice", drinknodes1);
+		assertNotNull(juice);
+		assertNotNull(juice.getPage());
+		assertEquals(1, juice.getChildren().size());
+		Vector<HierarchyNode> juicenodes1 = new Vector<HierarchyNode>();
+		juicenodes1.addAll(juice.getChildren());
+		String[] exp3 = {"Juice Apple"};
+		testNodeResults(juicenodes1, exp3);
+
+		HierarchyNode food = getNode("Food", nodes0);
+		assertNotNull(food);
+		assertNotNull(food.getPage());
+		assertEquals(3, food.getChildren().size());
+		Vector<HierarchyNode> foodnodes = new Vector<HierarchyNode>();
+		foodnodes.addAll(food.getChildren());
+		String[] exppie = {"Baklava", "Food Fruit", "Pie"};
+		testNodeResults(foodnodes, exppie);
+		
+		HierarchyNode piefruit = getNode("Pie", foodnodes);
+		assertNotNull(piefruit);
+		assertNull(piefruit.getPage());
+		assertEquals(2, piefruit.getChildren().size());
+		Vector<HierarchyNode> pienodes2 = new Vector<HierarchyNode>();
+		pienodes2.addAll(piefruit.getChildren());
+		String[] exppie2 = {"Pie Apple","Pie Fruit"};
+		testNodeResults(pienodes2, exppie2);
+		
+		HierarchyNode piefruit2 = getNode("Pie Fruit", pienodes2);
+		assertNotNull(piefruit2);
+		assertNotNull(piefruit2.getPage());
+		assertEquals(1, piefruit2.getChildren().size());
+		Vector<HierarchyNode> pienodes3 = new Vector<HierarchyNode>();
+		pienodes3.addAll(piefruit2.getChildren());
+		String[] expfruit = {"Pie Fruit Apple"};
+		testNodeResults(pienodes3, expfruit);
+	}
+	
+	public void testBuildHierarchy_sethomepagetitle() {
+		Properties props = tester.getProperties();
+		props.setProperty("collision-titles-food", "Apple,Fruit");
+		props.put("filepath-hierarchy-ext", "");
+		String samplepath = "sampleData/hierarchy/dokuwiki-nodehome"; 
+		props.put("filepath-hierarchy-ignorable-ancestors", samplepath);
+		//set a property to identify the position of the homepage file
+		props.put("hierarchy-homepage-position", "sibling"); //default is child
+		//set a property to identify the homepage file 
+		props.put("hierarchy-homepage-dokuwiki-filename", ""); //default is empty. means the nodename
+		tester.setProperties(props);
+		
+		File sampledir = new File(samplepath);
+		Collection<Page> pages = new Vector<Page>();
+		assertTrue(sampledir.exists());
+		File[] files = sampledir.listFiles(new NoSvnFilter());
+		pages = createPages(pages, files);
+
+		HierarchyNode root = tester.buildHierarchy(pages);
+		assertNotNull(root); //root node
+		assertNull(root.getName());
+		assertNull(root.getPage());
+		assertNull(root.getParent());
+		assertNotNull(root.getChildren());
+		
+		Set<HierarchyNode> top = root.getChildren();
+		assertEquals(2, top.size());
+		Vector<HierarchyNode> nodes0 = new Vector<HierarchyNode>();
+		nodes0.addAll(top);
+		String[] exp = {"Drink", "Food" };
+		testNodeResults(nodes0, exp);
+		
+		//needs more than one level of parent to avoid collision
+		HierarchyNode drink = getNode("Drink", nodes0);
+		assertNotNull(drink);
+		assertNotNull(drink.getPage());
+		assertEquals(2, drink.getChildren().size());
+		Vector<HierarchyNode> drinknodes1 = new Vector<HierarchyNode>();
+		drinknodes1.addAll(drink.getChildren());
+		String[] exp2 = {"Juice", "Water"};
+		testNodeResults(drinknodes1, exp2);
+		
+		HierarchyNode juice = getNode("Juice", drinknodes1);
+		assertNotNull(juice);
+		assertNotNull(juice.getPage());
+		assertEquals(1, juice.getChildren().size());
+		Vector<HierarchyNode> juicenodes1 = new Vector<HierarchyNode>();
+		juicenodes1.addAll(juice.getChildren());
+		String[] exp3 = {"Juice Apple"};
+		testNodeResults(juicenodes1, exp3);
+
+		HierarchyNode food = getNode("Food", nodes0);
+		assertNotNull(food);
+		assertNotNull(food.getPage());
+		assertEquals(3, food.getChildren().size());
+		Vector<HierarchyNode> foodnodes = new Vector<HierarchyNode>();
+		foodnodes.addAll(food.getChildren());
+		String[] exppie = {"Baklava", "Food Fruit", "Pie"};
+		testNodeResults(foodnodes, exppie);
+		
+		HierarchyNode piefruit = getNode("Pie", foodnodes);
+		assertNotNull(piefruit);
+		assertNull(piefruit.getPage());
+		assertEquals(2, piefruit.getChildren().size());
+		Vector<HierarchyNode> pienodes2 = new Vector<HierarchyNode>();
+		pienodes2.addAll(piefruit.getChildren());
+		String[] exppie2 = {"Pie Apple","Pie Fruit"};
+		testNodeResults(pienodes2, exppie2);
+		
+		HierarchyNode piefruit2 = getNode("Pie Fruit", pienodes2);
+		assertNotNull(piefruit2);
+		assertNotNull(piefruit2.getPage());
+		assertEquals(1, piefruit2.getChildren().size());
+		Vector<HierarchyNode> pienodes3 = new Vector<HierarchyNode>();
+		pienodes3.addAll(piefruit2.getChildren());
+		String[] expfruit = {"Pie Fruit Apple"};
+		testNodeResults(pienodes3, expfruit);
+	}
+	
 	private Collection<Page> createPages(Collection<Page> pages, File[] files) {
 		for (File file : files) {
 			if (file.getName().endsWith(".swp")) continue;
diff --git a/src/com/atlassian/uwc/hierarchies/FilepathHierarchy.java b/src/com/atlassian/uwc/hierarchies/FilepathHierarchy.java
index 00bab1d..6102e31 100644
--- a/src/com/atlassian/uwc/hierarchies/FilepathHierarchy.java
+++ b/src/com/atlassian/uwc/hierarchies/FilepathHierarchy.java
@@ -238,7 +238,8 @@
 			}
 			else
 				thisname = child.getName();
-			if (childname.equals(thisname)) {
+			log.debug("...... -> comparing child with: " + thisname);
+			if (childname.equalsIgnoreCase(thisname)) {
 				log.debug("...... -> found child.");
 				return child;
 			}
diff --git a/src/com/atlassian/uwc/ui/Comment.java b/src/com/atlassian/uwc/ui/Comment.java
index 68b59f5..b4b1647 100644
--- a/src/com/atlassian/uwc/ui/Comment.java
+++ b/src/com/atlassian/uwc/ui/Comment.java
@@ -5,6 +5,8 @@
 	public String text;
 	public String creator;
 	public String timestamp;//yyyy:MM:dd:HH:mm:ss:SS
+	//by default we set this to false. When false the engine will ask Confluence to transform markup to xhtml
+	private boolean isXhtml = false; 
 	
 	public Comment() {
 		
@@ -19,6 +21,13 @@
 		this.creator = creator;
 		this.timestamp = timestamp;
 	}
+	
+	public Comment(String text, String creator, String timestamp, boolean isXhtml) {
+		this(text);
+		this.creator = creator;
+		this.timestamp = timestamp;
+		this.isXhtml = isXhtml;
+	}
 
 	public boolean hasCreator() {
 		return creator != null;
@@ -27,4 +36,8 @@
 	public boolean hasTimestamp() {
 		return timestamp != null;
 	}
+
+	public boolean isXhtml() {
+		return this.isXhtml; 
+	}
 }
diff --git a/src/com/atlassian/uwc/ui/ConverterEngine.java b/src/com/atlassian/uwc/ui/ConverterEngine.java
index e8b17c9..1c0bbd0 100644
--- a/src/com/atlassian/uwc/ui/ConverterEngine.java
+++ b/src/com/atlassian/uwc/ui/ConverterEngine.java
@@ -1996,7 +1996,11 @@
     				//create page that broker can use

     				CommentForXmlRpc brokerComment = new CommentForXmlRpc();

     				brokerComment.setPageId(pageId);

-    				brokerComment.setContent(getContentAsXhtmlFormat(broker, confSettings, comment.text));

+    				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()) {

diff --git a/src/com/atlassian/uwc/ui/UWCForm3.java b/src/com/atlassian/uwc/ui/UWCForm3.java
index d8bb85c..76655ce 100644
--- a/src/com/atlassian/uwc/ui/UWCForm3.java
+++ b/src/com/atlassian/uwc/ui/UWCForm3.java
@@ -92,7 +92,7 @@
 	private static final String LOGIN_TOOLTIP = "This is the username of an account on the Confluence server with write privileges to the space where you\'re adding content.";
 	private static final String ADDRESS_TOOLTIP = "This is the url to Confluence. Example: 'localhost:8080'";
 	private static final String VERSION_INDICATOR = ""; 
-	public static final String VERSION_NUMBER = "4.0-SNAPSHOT-090412";
+	public static final String VERSION_NUMBER = "4.0-SNAPSHOT-090712";
 	private static final Dimension TABBEDPANE_SIZE = new Dimension(420, 475);
 	public static final String APP_NAME = "Universal Wiki Converter";
 	private static final int BASIC_RIGHT_MARGIN = 35;