dokuwiki, Improved Link handling, Improved Code Block handling (leading spaces)
diff --git a/conf/converter.dokuwiki.properties b/conf/converter.dokuwiki.properties
index 16d5854..dbd9779 100644
--- a/conf/converter.dokuwiki.properties
+++ b/conf/converter.dokuwiki.properties
@@ -45,13 +45,19 @@
 ## correspond to your pages setting, but with "media" instead of "pages" as the penultimate directory

 #DokuWiki.001.attachmentdirectory.property=/Absolute/path/to/media/dir::/Absolute/path/to/media/foo

 # 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}\\{

+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.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

+DokuWiki.003.leadingspacestocode.class=com.atlassian.uwc.converters.dokuwiki.LeadingSpacesConverter

+# We redo this so that the results of leadingspaces are tokenized

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

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

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

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

-DokuWiki.0.tags.class=com.atlassian.uwc.converters.dokuwiki.TagConverter

+DokuWiki.010.tags.class=com.atlassian.uwc.converters.dokuwiki.TagConverter

 # Text formating

 DokuWiki.1bold.perl=s/\*\*([^*]+?)\*\*/*$1*/g

 DokuWiki.1italic.perl=s/(?s)([^:])\/\/(.+?)\/\//$1_$2_/g

@@ -94,7 +100,7 @@
 # ====================================================================================================================

 

 # Lists

-DokuWiki.lists.class=com.atlassian.uwc.converters.dokuwiki.ListConverter

+DokuWiki.3.lists.class=com.atlassian.uwc.converters.dokuwiki.ListConverter

 

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

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

@@ -112,6 +118,8 @@
 DokuWiki.3interwiki_wp1.perl=s/\[\[[\\s]*wp>([^\|\]]*)\|([^\]]*)\]/[[http:\/\/en.wikipedia.org\/wiki\/$1|$2]/g

 # Match [wp>...]

 DokuWiki.3interwiki_wp2.perl=s/\[\[[\\s]*wp>([^\]\|]*)\]/[[http:\/\/en.wikipedia.org\/wiki\/$1|$1]/g

+# Match [[\\some\windows\share]]

+#DokuWiki.3.winshare.java-regex=\[\[(\\\\.*?)\]\]{replace-with}[$1]

 

 ## Images 

 ## If using DokuwikiHierarchy, comment DokuWikiImageConverter, and uncomment HierarchyImageConverter

@@ -174,8 +182,10 @@
 #DokuWiki.7.userdata.class=com.atlassian.uwc.converters.dokuwiki.DokuwikiUserDate

 

 # Detokenize (code blocks)

-DokuWiki.90.detokenizer.class=com.atlassian.uwc.converters.DetokenizerConverter

+DokuWiki.91.detokenizer.class=com.atlassian.uwc.converters.DetokenizerConverter

 

+# Tokenize existing html tags

+DokuWiki.92.htmltags.java-regex-tokenizer=(?s)<html>(.*?)</html>{replace-with}$1

 # Transform to Confluence XHTML Format as a converter

 DokuWiki.95.confluencemarkuptoxhtml.class=com.atlassian.uwc.converters.ConfluenceMarkupToXhtml

 # Don't allow the engine to run the transformation

@@ -183,3 +193,10 @@
 # Handle Table col and rowspans

 DokuWiki.96.table-rowandcolspans.class=com.atlassian.uwc.converters.dokuwiki.TableRowColSpanConverter

 

+# Detokenize (html tags) - we do this to address cases where the users had

+# Dokuwiki markup and html tagged markup. DokuWiki.92 tokenized the tags before

+# the call to get the atlassian xhtml, but then the tokens were transformed so

+# we have to fix them with 97 and 98 before we can detokenize

+DokuWiki.97.fixtokenizertokens-start.java-regex=<sub>(?=UWCTOKENSTART){replace-with}~

+DokuWiki.98.fixtokenizertokens-end.java-regex=(?<=UWCTOKENEND)</sub>{replace-with}~

+DokuWiki.99.detokenizer.class=com.atlassian.uwc.converters.DetokenizerConverter

diff --git a/sampleData/dokuwiki/confluenceSettings.properties b/sampleData/dokuwiki/confluenceSettings.properties
index 52e7e5e..882022f 100644
--- a/sampleData/dokuwiki/confluenceSettings.properties
+++ b/sampleData/dokuwiki/confluenceSettings.properties
@@ -15,5 +15,4 @@
 feedback.option=true
 password=admin
 wikitype=dokuwiki
-pages=/Users/laura/Code/Subversion/uwc-spac/devel/sampleData/dokuwiki/SampleDokuwiki-InputCode.txt::/Users/laura/Code/Subversion/uwc-spac/devel/sampleData/dokuwiki/SampleDokuwiki-InputDiscussion.txt::/Users/laura/Code/Subversion/uwc-spac/devel/sampleData/dokuwiki/SampleDokuwiki-InputEsc.txt::/Users/laura/Code/Subversion/uwc-spac/devel/sampleData/dokuwiki/SampleDokuwiki-InputExt.txt::/Users/laura/Code/Subversion/uwc-spac/devel/sampleData/dokuwiki/SampleDokuwiki-InputLists.txt::/Users/laura/Code/Subversion/uwc-spac/devel/sampleData/dokuwiki/SampleDokuwiki-InputMailto.txt::/Users/laura/Code/Subversion/uwc-spac/devel/sampleData/dokuwiki/SampleDokuwiki-InputManfred.txt::/Users/laura/Code/Subversion/uwc-spac/devel/sampleData/dokuwiki/SampleDokuwiki-InputOptLink.txt
-#pages=/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputTables.txt
+pages=/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputCode.txt::/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputDiscussion.txt::/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputEsc.txt::/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputExt.txt::/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputLists.txt::/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputMailto.txt::/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputManfred.txt::/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputOptLink.txt::/Users/laura/Code/Git/uwc/sampleData/dokuwiki/SampleDokuwiki-InputTables.txt
diff --git a/sampleData/dokuwiki/junit_resources/meta/foo.meta b/sampleData/dokuwiki/junit_resources/meta/foo.meta
new file mode 100644
index 0000000..5f0e021
--- /dev/null
+++ b/sampleData/dokuwiki/junit_resources/meta/foo.meta
@@ -0,0 +1,2 @@
+a:2:{s:7:"current";a:10:{s:4:"date";a:2:{s:7:"created";i:1317381420;s:8:"modified";i:1321430495;}s:7:"creator";s:13:"Myrna Loy";s:11:"last_change";a:7:{s:4:"date";i:1321430495;s:2:"ip";s:9:"10.1.3.33";s:4:"type";s:1:"E";s:2:"id";s:64:"sampledokuwikiinputtitle";s:4:"user";s:3:"mloy";s:3:"sum";s:29:"";s:5:"extra";s:0:"";}s:11:"contributor";a:2:{s:3:"foo";s:13:"Myrna Loy";s:3:"ast";s:13:"Asta";}s:4:"user";s:0:"";s:5:"title";s:25:"Foo tralala";s:11:"description";a:2:{s:15:"tableofcontents";a:0:{};s:8:"abstract";s:353:"Testing...
+	";}s:7:"subject";a:0:{};s:8:"internal";a:2:{s:5:"cache";b:1;s:3:"toc";b:1;}s:8:"relation";a:1:{s:10:"firstimage";s:59:"";}}s:10:"persistent";a:5:{s:4:"date";a:2:{s:7:"created";i:1317381420;s:8:"modified";i:1321430495;}s:7:"creator";s:13:"Myrna Loy";s:11:"last_change";a:7:{s:4:"date";i:1321430495;s:2:"ip";s:9:"10.1.3.33";s:4:"type";s:1:"E";s:2:"id";s:64:"";s:4:"user";s:3:"";s:3:"sum";s:29:" ";s:5:"extra";s:0:"";}s:11:"contributor";a:2:{s:3:"ml";s:13:"ML";s:3:"as";s:13:"asta";}s:4:"user";s:0:"";}}
diff --git a/sampleData/dokuwiki/junit_resources/meta/foo/bar.meta b/sampleData/dokuwiki/junit_resources/meta/foo/bar.meta
new file mode 100644
index 0000000..8a3b12c
--- /dev/null
+++ b/sampleData/dokuwiki/junit_resources/meta/foo/bar.meta
@@ -0,0 +1,2 @@
+a:2:{s:7:"current";a:10:{s:4:"date";a:2:{s:7:"created";i:1317381420;s:8:"modified";i:1321430495;}s:7:"creator";s:13:"Myrna Loy";s:11:"last_change";a:7:{s:4:"date";i:1321430495;s:2:"ip";s:9:"10.1.3.33";s:4:"type";s:1:"E";s:2:"id";s:64:"sampledokuwikiinputtitle";s:4:"user";s:3:"mloy";s:3:"sum";s:29:"";s:5:"extra";s:0:"";}s:11:"contributor";a:2:{s:3:"foo";s:13:"Myrna Loy";s:3:"ast";s:13:"Asta";}s:4:"user";s:0:"";s:5:"title";s:25:"Harumph BAr";s:11:"description";a:2:{s:15:"tableofcontents";a:0:{};s:8:"abstract";s:353:"Testing...
+	";}s:7:"subject";a:0:{};s:8:"internal";a:2:{s:5:"cache";b:1;s:3:"toc";b:1;}s:8:"relation";a:1:{s:10:"firstimage";s:59:"";}}s:10:"persistent";a:5:{s:4:"date";a:2:{s:7:"created";i:1317381420;s:8:"modified";i:1321430495;}s:7:"creator";s:13:"Myrna Loy";s:11:"last_change";a:7:{s:4:"date";i:1321430495;s:2:"ip";s:9:"10.1.3.33";s:4:"type";s:1:"E";s:2:"id";s:64:"";s:4:"user";s:3:"";s:3:"sum";s:29:" ";s:5:"extra";s:0:"";}s:11:"contributor";a:2:{s:3:"ml";s:13:"ML";s:3:"as";s:13:"asta";}s:4:"user";s:0:"";}}
diff --git a/sampleData/dokuwiki/testall.sh b/sampleData/dokuwiki/testall.sh
index b4559ba..4bea6a2 100755
--- a/sampleData/dokuwiki/testall.sh
+++ b/sampleData/dokuwiki/testall.sh
@@ -12,6 +12,9 @@
 echo "Lists"
 ./compare.sh Lists
 
+echo "Tables"
+./compare.sh Tables 
+
 ## Not completely implemented yet
 #echo "Image"
 #./compare.sh Image
diff --git a/src/com/atlassian/uwc/converters/LeadingSpacesBaseConverter.java b/src/com/atlassian/uwc/converters/LeadingSpacesBaseConverter.java
new file mode 100644
index 0000000..a3de079
--- /dev/null
+++ b/src/com/atlassian/uwc/converters/LeadingSpacesBaseConverter.java
@@ -0,0 +1,73 @@
+package com.atlassian.uwc.converters;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.atlassian.uwc.converters.tikiwiki.RegexUtil;
+import com.atlassian.uwc.ui.Page;
+
+/**
+ * @author Laura Kolker
+ * Utility methods for transforming a syntax that starts each line into a block contained with outer delimiters.
+ * See dokuwiki's LeadingSpacesConverter for example of usage.
+ */
+public abstract class LeadingSpacesBaseConverter extends BaseConverter {
+
+	protected String initialspacedelim = " ";
+	private String newline = "\n";
+	private String noNewlines = "[^\n]+";
+	private String optNoSpace = "([^ ]?)";
+	private String leadingSpaceLine = "(" + initialspacedelim + noNewlines + newline + ")";
+	private String manyLeadingSpaceLines = "(" + leadingSpaceLine + "+)";
+	private String regex = "(?:\n|^)" + manyLeadingSpaceLines + optNoSpace;
+	/**
+	 * assumes one leading space. If you want to customize the pattern, use
+	 * generateLeadingPattern 
+	 */
+	protected Pattern leadingSpacesPattern = Pattern.compile(regex);
+	
+	public String convertLeadingSpacesReplaceAll(String input, Pattern pattern, String replacement) {
+		try {
+			Matcher m = pattern.matcher(input);
+			if (m.find()) {
+				log.debug("Leading Spaces - regex found: " + m.group());
+				return m.replaceAll(replacement);
+			}
+		} catch (StackOverflowError e) {
+			log.debug("Too much backtracking. Skipping.");
+		}
+		return input;
+	}
+	
+	public String convertLeadingSpacesLoop(String input, String regex, String replacement) {
+		return RegexUtil.loopRegex(input, regex, replacement);
+	}
+	
+	public String getReplacement(String delim) {
+		return getReplacement(delim, delim);
+	}
+	
+	public String getReplacement(String delim, String enddelim) {
+		return "\n" + delim +
+				"\n$1" + enddelim + 
+				"\n$3";
+	}
+	
+	public String getReplacementLoopUtil(String delim, String enddelim) {
+		return "\n" + delim +
+				"\n{group1}" + enddelim + 
+				"\n{group3}";
+	}
+
+	/**
+	 * @param initialspacedelim initial ws delimiter that must be present to presume the line
+	 * is a leading spaces line
+	 * @return 
+	 */
+	protected String generateLeadingPattern(String initialspacedelim) {
+		String leadingSpaceLine = "(" + initialspacedelim + noNewlines + newline + ")";
+		String manyLeadingSpaceLines = "(" + leadingSpaceLine + "+)";
+		String regex = "(?:\n|^)" + manyLeadingSpaceLines + optNoSpace;
+		return regex;
+	}
+}
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
index d541602..95ed00a 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/DokuwikiUserDate.java
@@ -12,12 +12,13 @@
 import com.atlassian.uwc.ui.FileUtils;
 import com.atlassian.uwc.ui.Page;
 
-public class DokuwikiUserDate extends BaseConverter {
+public class DokuwikiUserDate extends HierarchyTarget {
 
 	Logger log = Logger.getLogger(this.getClass());
 	public void convert(Page page) {
 		String changeFilepath = createChangeFilename(page.getFile().getPath());
 		if (changeFilepath == null) {
+			log.warn("Could not handle user and date data. Check filepath-hierarchy-ignorable-ancestors amd meta-dir settings. Skipping");
 			return;
 		}
 		File changeFile = new File(changeFilepath);
@@ -50,25 +51,7 @@
 	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");
-			return null;
-		}
-		String ignorable = getProperties().getProperty("filepath-hierarchy-ignorable-ancestors", null);
-		if (ignorable == null) {
-			log.warn("Could not handle user and date data. filepath-hierarchy-ignorable-ancestors must be set. Skipping");
-			return null;
-		}
-		String relative = path.replaceFirst("\\Q" + ignorable + "\\E", "");
-		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))
-			relative = File.separator + relative;
-		return metadir + relative;
-	}
+	
 
 	Pattern lastline = Pattern.compile("[^\n]*$");
 	private String getLastLine(String input) {
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
index f798df9..e8accdb 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverter.java
@@ -1,19 +1,22 @@
 package com.atlassian.uwc.converters.dokuwiki;
 
+import java.io.File;
 import java.util.HashMap;
 import java.util.Vector;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import com.atlassian.uwc.converters.IllegalLinkNameConverter;
+import org.apache.log4j.Logger;
+
 import com.atlassian.uwc.converters.tikiwiki.RegexUtil;
 import com.atlassian.uwc.ui.Page;
 
 public class HierarchyLinkConverter extends HierarchyTarget {
 
+	Logger log = Logger.getLogger(this.getClass());
 	public void convert(Page page) {
 		String input = page.getOriginalText();
-		String converted = convertLink(input, getCurrentPath(page), getSpacekey(page));
+		String converted = convertLink(input, getCurrentPath(page), getSpacekey(page), page.getFile().getPath());
 		page.setConvertedText(converted);
 
 	}
@@ -28,9 +31,9 @@
 	Pattern link = Pattern.compile("(?<=\\[)\\[([^\\]]*)\\](?=\\])");
 	Pattern onecolon = Pattern.compile("^([^:]*:)([^:]*)$");
 	protected String convertLink(String input) {
-		return convertLink(input, null, getSpacekey(null));
+		return convertLink(input, null, getSpacekey(null), "");
 	}
-	protected String convertLink(String input, String currentPath, String spacekey) {
+	protected String convertLink(String input, String currentPath, String spacekey, String pagepath) {
 		Matcher linkFinder = link.matcher(input);
 		String currentSpacekey = spacekey;
 		Vector<String> allspaces = getSpaces();
@@ -59,30 +62,46 @@
 				}
 				if (target.startsWith(":")) target = target.replaceFirst(":", "");
 				//figure out if we've already got the space represented
-				String targetPart1 = target.replaceFirst(":.*$", "");
+				String targetPart1 = target.replaceFirst(":[^:]+$", "");
 				boolean containsSpace = false;
 				if (allspaces.contains(targetPart1)) 
 					containsSpace = true;
+				log.debug("targetPart1 =" + targetPart1);
 				//get rid of unnecessary links to start 
 				//(start page content will be moved to parent in DokuwikiHierarchy
 				//unless the start page is a top level page in the space)
-				if (!(containsSpace && (onecolon.matcher(target)).matches())) 
+				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);
+				String metaFilename = getMetaFilename(pagepath, ".meta");
+//				log.debug("isOne = " + isOne + ", target = " + target + ", metaFilename = " + metaFilename);
+				metaFilename = getTargetMetaFilename(target, metaFilename, isOne);
+				log.debug("metaFilename = " + metaFilename);
+				String metatitle = HierarchyTitleConverter.getMetaTitle(metaFilename);
+				log.debug("metatitle = " + metatitle);
 				//get confluence page name and fix the case to match HierarchyTitleConverter
-				target = target.replaceFirst("^.*:", ""); //remove everything to the last colon
+				if (metatitle == null || "".equals(metatitle)) 
+					target = target.replaceFirst("^.*:", ""); //remove everything to the last colon
+				else //title was set with metadata
+					target = metatitle;
 				target = HierarchyTitleConverter.casify(target); //foo_bar becomes Foo Bar
 				//fix collisions
 				String linkSpacekey = currentSpacekey;
+				targetPart1 = targetPart1.replaceAll(":", File.separator);
+				log.debug("containsSpace: " + containsSpace + ", ns: "+ namespaces.containsKey(targetPart1));
 				if (!containsSpace && namespaces.containsKey(targetPart1)) {
 					linkSpacekey = namespaces.get(targetPart1); 
 				}
 				if (containsSpace) linkSpacekey = targetPart1;
-				target = fixCollisions(target, hierarchy, linkSpacekey);
+//				target = fixCollisions(target, hierarchy, linkSpacekey);//collisions handling has to happen elsewhere?
 				//underscores to spaces
 				target = target.replaceAll("_", " ");
+				log.debug("link target = " + target);
 				//add spacekey to target if necessary
 				if (!target.contains(":") || containsSpace) 
 					target = linkSpacekey + ":" + target;
@@ -101,6 +120,33 @@
 		}
 		return input;
 	}
+	
+	
+	Pattern metaFile = Pattern.compile("([\\\\/])([^\\\\/]+)(\\.meta)$");
+	protected String getTargetMetaFilename(String target, String metaFilename, boolean isOne) {
+		target=target.replaceAll(":+", File.separator);
+		if (!target.startsWith(File.separator)) target = File.separator + target;
+		if (!isOne) {
+			String metadir = getProperties().getProperty("meta-dir", null);
+			return metadir + target + ".meta";  
+		}
+		Matcher metaFinder = metaFile.matcher(metaFilename);
+		StringBuffer sb = new StringBuffer();
+		boolean found = false;
+		if (metaFinder.find()) {
+			found = true;
+			String replacement = target + metaFinder.group(3);
+			
+			replacement = RegexUtil.handleEscapesInReplacement(replacement);
+			metaFinder.appendReplacement(sb, replacement);
+		}
+		if (found) {
+			metaFinder.appendTail(sb);
+			return sb.toString();
+		}
+		return null;
+	}
+
 	Pattern protocol = Pattern.compile("(https?:)|(ftp:)");
 	private boolean isExternal(String target) {
 		Matcher protocolFinder = protocol.matcher(target);
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
index 8df5ef7..79fc7e7 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyLinkConverterTest.java
@@ -1,5 +1,7 @@
 package com.atlassian.uwc.converters.dokuwiki;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.Properties;
 
 import junit.framework.TestCase;
@@ -7,6 +9,7 @@
 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;
 
+import com.atlassian.uwc.ui.FileUtils;
 import com.atlassian.uwc.ui.Page;
 
 public class HierarchyLinkConverterTest extends TestCase {
@@ -26,6 +29,9 @@
 		props.put("filepath-hierarchy-ext", "");
 		props.put("filepath-hierarchy-ignorable-ancestors", "sampleData/hierarchy/dokuwiki");
 		tester.setProperties(props);
+
+		tester.getProperties().setProperty("meta-dir", HierarchyTitleConverterTest.METADIR);
+		tester.getProperties().setProperty("filepath-hierarchy-ignorable-ancestors", HierarchyTitleConverterTest.PAGESDIR);
 	}
 	
 	public void testConvertLink() {
@@ -88,7 +94,7 @@
 				"[Fruit Drink|food:Drink Fruit]\n" +
 				"[food:Cranberry]";
 		String spacekey = "food";
-		actual = tester.convertLink(input, "drink/", spacekey);
+		actual = tester.convertLink(input, "drink/", spacekey, "");
 		assertNotNull(actual);
 		assertEquals(expected, actual);
 	}
@@ -103,7 +109,7 @@
 	}
 	
 	public void testConvertWithPageByPageSpaces() {
-		Page page = new Page(null);
+		Page page = new Page(new File(HierarchyTitleConverterTest.PAGESDIR+"/SampleDokuwiki-InputTitle.txt"));
 		page.setOriginalText("[[.:home]]\n" +
 				"[[drink:start]]\n");
 		String spacekey = "otherspace";
@@ -115,4 +121,44 @@
 		assertNotNull(actual);
 		assertEquals(expected, actual);
 	}
+	
+	public void testConvertWithMetaTitle() throws IOException {
+		String input, expected, actual;
+		input = "[[.:foo]]\n" +
+				"[[:foo:bar]]\n";
+		expected = "[xyz:Foo Tralala]\n" +
+				"[xyz:Harumph BAr]\n";
+		tester.getProperties().setProperty("filepath-hierarchy-ignorable-ancestors", "/Users/laura/Code/Git/uwc/sampleData/dokuwiki/junit_resources/pages");
+		String pretendthispagepath = "/Users/laura/Code/Git/uwc/sampleData/dokuwiki/junit_resources/pages/test.txt";
+		actual = tester.convertLink(input, "", "xyz", pretendthispagepath);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+	}
+	
+	public void testGetTargetMetaFilename() {
+		String input, expected, actual;
+		input = "foo";
+		String filename = HierarchyTitleConverterTest.METADIR+"/home.meta";
+		expected = HierarchyTitleConverterTest.METADIR+"/foo.meta";
+		actual = tester.getTargetMetaFilename(input, filename, true);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		
+		input = "abc:def:ghi::jkl";
+		filename = HierarchyTitleConverterTest.METADIR+"/abc/def/ghi/home.meta";
+		expected = HierarchyTitleConverterTest.METADIR+"/abc/def/ghi/jkl.meta";
+		actual = tester.getTargetMetaFilename(input, filename, false);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testConvertWithAnotherSpacekey() {
+		fail();
+	}
+	
+	public void testConvertWithCollision() {
+		fail();
+	}
 }
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTarget.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTarget.java
index b7e657b..5ceecfb 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTarget.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTarget.java
@@ -1,5 +1,6 @@
 package com.atlassian.uwc.converters.dokuwiki;
 
+import java.io.File;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Properties;
@@ -88,4 +89,22 @@
 		return full.replaceAll("\\Q"+ignorable + "\\E", "");
 	}
 
+	protected String getMetaFilename(String path, String filetype) {
+		String metadir = getProperties().getProperty("meta-dir", null);
+		if (metadir == null) {
+			return null;
+		}
+		String ignorable = getProperties().getProperty("filepath-hierarchy-ignorable-ancestors", null);
+		if (ignorable == null) {
+			return null;
+		}
+		String relative = path.replaceFirst("\\Q" + ignorable + "\\E", "");
+		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))
+			relative = File.separator + relative;
+		return metadir + relative;
+	}
+	
 }
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTitleConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTitleConverter.java
index fcdf4d8..bbe0844 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTitleConverter.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTitleConverter.java
@@ -12,7 +12,7 @@
 
 public class HierarchyTitleConverter extends DokuwikiUserDate {
 
-	Logger log = Logger.getLogger(this.getClass());
+	static Logger log = Logger.getLogger(HierarchyTitleConverter.class);
 	public void convert(Page page) {
 		String name = page.getName();
 		//check the metadata for a title
@@ -23,26 +23,32 @@
 		page.setName(name);
 	}
 	
-	Pattern title = Pattern.compile("s:5:\"title\";" + 
+	static Pattern title = Pattern.compile("s:5:\"title\";" + 
 			"s:\\d+:\"(.*?)\";" + 
 			"s:11:\"description\";");
 	public String getMetaTitle(Page page) {
 		if (page == null) return page.getName();
 		if (page.getFile() == null) return page.getName();
 		String metaFilename = getMetaFilename(page.getFile().getPath(), ".meta");
-		if (metaFilename == null) return page.getName();
+		String title = getMetaTitle(metaFilename);
+		if (title == null) return page.getName();
+		return title;
+	}
+
+	public static String getMetaTitle(String metaFilename) {
+		if (metaFilename == null) return null;
 		String contents = null;
 		try {
 			contents = FileUtils.readTextFile(new File(metaFilename));
 		} catch (IOException e) {
-			log.debug("Problem reading meta file: " + metaFilename,e);
-			return page.getName();
+			log.debug("Problem reading meta file: " + metaFilename);
+			return null;
 		}
 		Matcher titleFinder = title.matcher(contents);
 		if (titleFinder.find()) {
 			return titleFinder.group(1);
 		}
-		return page.getName();
+		return null;
 	}
 
 	public static String fixTitle(String name) {
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTitleConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTitleConverterTest.java
index 1389752..b105e6a 100644
--- a/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTitleConverterTest.java
+++ b/src/com/atlassian/uwc/converters/dokuwiki/HierarchyTitleConverterTest.java
@@ -18,9 +18,9 @@
 	HierarchyTitleConverter tester = null;
 	Logger log = Logger.getLogger(this.getClass());
 	
-	private static final String METADIR = "sampleData/dokuwiki/junit_resources/meta";
+	static final String METADIR = "sampleData/dokuwiki/junit_resources/meta";
 	private static final String SAMPLEMETA = METADIR + "/SampleDokuwiki-InputTitle.meta";
-	private static final String PAGESDIR = "sampleData/dokuwiki/junit_resources/pages";
+	static final String PAGESDIR = "sampleData/dokuwiki/junit_resources/pages";
 	private static final String SAMPLEPAGE = PAGESDIR+"/SampleDokuwiki-InputTitle.txt";
 	
 	protected void setUp() throws Exception {
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/LeadingSpacesConverter.java b/src/com/atlassian/uwc/converters/dokuwiki/LeadingSpacesConverter.java
new file mode 100644
index 0000000..5ae7f46
--- /dev/null
+++ b/src/com/atlassian/uwc/converters/dokuwiki/LeadingSpacesConverter.java
@@ -0,0 +1,27 @@
+package com.atlassian.uwc.converters.dokuwiki;
+
+import com.atlassian.uwc.converters.LeadingSpacesBaseConverter;
+import com.atlassian.uwc.ui.Page;
+
+/**
+ * @author Laura Kolker
+ * transforms sets of lines starting with two ws into code blocks
+ */
+public class LeadingSpacesConverter extends LeadingSpacesBaseConverter {
+
+	protected String initialspacedelim = "  (?! *?[-*])"; //two spaces!
+
+	@Override
+	public void convert(Page page) {
+		String input = page.getOriginalText();
+		String converted = convertLeadingSpaces(input);
+		page.setConvertedText(converted);
+	}
+
+	protected String convertLeadingSpaces(String input) {
+		String replacement = getReplacementLoopUtil("<code>", "</code>");
+		String regex = generateLeadingPattern(this.initialspacedelim);
+		return convertLeadingSpacesLoop(input, regex, replacement);
+	}
+
+}
diff --git a/src/com/atlassian/uwc/converters/dokuwiki/LeadingSpacesConverterTest.java b/src/com/atlassian/uwc/converters/dokuwiki/LeadingSpacesConverterTest.java
new file mode 100644
index 0000000..e2bc560
--- /dev/null
+++ b/src/com/atlassian/uwc/converters/dokuwiki/LeadingSpacesConverterTest.java
@@ -0,0 +1,86 @@
+package com.atlassian.uwc.converters.dokuwiki;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+
+public class LeadingSpacesConverterTest extends TestCase {
+
+	LeadingSpacesConverter tester = null;
+	Logger log = Logger.getLogger(this.getClass());
+	protected void setUp() throws Exception {
+		tester = new LeadingSpacesConverter();
+		PropertyConfigurator.configure("log4j.properties");
+	}
+
+	public void testConvertLeadingSpaces() {
+		String input, expected, actual;
+		input = "testing 123\n" +
+				"  needs to be in code block\n" +
+				"  this part too\n" +
+				"not in code block\n" +
+				" not a code block2\n" +
+				"  but this is!\n" +
+				"  tralala\n" +
+				"     tralalala\n" +
+				"  tra!\n" +
+				"end";
+		expected = "testing 123\n" +
+				"<code>\n" +
+				"  needs to be in code block\n" +
+				"  this part too\n" +
+				"</code>\n" +
+				"not in code block\n" +
+				" not a code block2\n" +
+				"<code>\n" +
+				"  but this is!\n" +
+				"  tralala\n" +
+				"     tralalala\n" +
+				"  tra!\n" +
+				"</code>\n" +
+				"end";
+		actual = tester.convertLeadingSpaces(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+	
+	public void testConvertLeadingSpaces_NotLists() {
+		String input, expected, actual;
+		input = "testing 123\n" +
+				"  * this is a list not a code block\n" +
+				"  * this part too\n" +
+				"end of list \n";
+		expected = "testing 123\n" +
+				"  * this is a list not a code block\n" +
+				"  * this part too\n" +
+				"end of list \n";
+		actual = tester.convertLeadingSpaces(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		input = "testing 123\n" +
+				"  * this is a list not a code block\n" +
+				"    * this part too\n" +
+				"end of list \n";
+		expected = "testing 123\n" +
+				"  * this is a list not a code block\n" +
+				"    * this part too\n" +
+				"end of list \n";
+		actual = tester.convertLeadingSpaces(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+		
+		input = "testing 123\n" +
+				"  - this is a list not a code block\n" +
+				"  - this part too\n" +
+				"end of list \n";
+		expected = "testing 123\n" +
+				"  - this is a list not a code block\n" +
+				"  - this part too\n" +
+				"end of list \n";
+		actual = tester.convertLeadingSpaces(input);
+		assertNotNull(actual);
+		assertEquals(expected, actual);
+	}
+}
diff --git a/src/com/atlassian/uwc/converters/mediawiki/LeadingSpacesConverter.java b/src/com/atlassian/uwc/converters/mediawiki/LeadingSpacesConverter.java
index 06634ca..1eea214 100644
--- a/src/com/atlassian/uwc/converters/mediawiki/LeadingSpacesConverter.java
+++ b/src/com/atlassian/uwc/converters/mediawiki/LeadingSpacesConverter.java
@@ -6,6 +6,7 @@
 import org.apache.log4j.Logger;
 
 import com.atlassian.uwc.converters.BaseConverter;
+import com.atlassian.uwc.converters.LeadingSpacesBaseConverter;
 import com.atlassian.uwc.converters.tikiwiki.RegexUtil;
 import com.atlassian.uwc.ui.Page;
 
@@ -27,21 +28,9 @@
  * @author Laura Kolker
  *
  */
-public class LeadingSpacesConverter extends BaseConverter {
+public class LeadingSpacesConverter extends LeadingSpacesBaseConverter {
 	Logger log = Logger.getLogger(this.getClass());
-
-	//regex parts
-	String newline = "\n";
-	String space = " ";
-	String noNewlines = "[^\n]+";
-	String leadingSpaceLine = "(" + space + noNewlines + newline + ")"; //2nd group
-	String manyLeadingSpaceLines = "(" + leadingSpaceLine + "+)"; //1st group
-	String optNoSpace = "([^ ]?)"; //3rd group
-	//is equivalent to: String regex = "\n(( [^\n]+\n)+)([^ ])?";
-	String regex = "(?:\n|^)" + manyLeadingSpaceLines + optNoSpace;
-	Pattern p = Pattern.compile(regex);
-
-	Pattern leadingspaces = Pattern.compile("" +
+	private Pattern leadingspaces = Pattern.compile("" +
 			"(?<=\n|^) +[^\n]+");
 	public void convert(Page page) {
 		log.debug("Converting Leading Spaces - starting");
@@ -70,29 +59,18 @@
 		}
 		else {
 			log.debug("leading spaces -> panel");
-			String replacement = getReplacement(); //newlines were giving me trouble in the properties file
-			try {
-				Matcher m = p.matcher(input);
-				if (m.find()) {
-					log.debug("Leading Spaces - regex found: " + m.group());
-					converted = m.replaceAll(replacement);
-				}
-			} catch (StackOverflowError e) {
-				log.debug("Too much backtracking. Skipping.");
-				return;
-			}
+			converted = convertLeadingSpacesReplaceAll(input, leadingSpacesPattern, getReplacement());
 		}
 		
 		page.setConvertedText(converted);
 		log.debug("Converting Leading Spaces - complete");
 	}
 	
+	
 	private String getReplacement() {
 		String delim = getProperties().getProperty("leading-spaces-delim", "panel");
 		log.debug("Leading spaces replacement delim: " + delim);
-		return "\n{" + delim +
-				"}\n$1{" + delim + 
-				"}\n$3";
+		return getReplacement("{"+delim+"}");
 	}
 
 }
diff --git a/src/com/atlassian/uwc/converters/mediawiki/LeadingSpacesConverterTest.java b/src/com/atlassian/uwc/converters/mediawiki/LeadingSpacesConverterTest.java
index c03ba06..ab349ca 100644
--- a/src/com/atlassian/uwc/converters/mediawiki/LeadingSpacesConverterTest.java
+++ b/src/com/atlassian/uwc/converters/mediawiki/LeadingSpacesConverterTest.java
@@ -7,11 +7,12 @@
 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;
 
+import com.atlassian.uwc.converters.LeadingSpacesBaseConverter;
 import com.atlassian.uwc.ui.Page;
 
 public class LeadingSpacesConverterTest extends TestCase {
 
-	LeadingSpacesConverter tester = null;
+	LeadingSpacesBaseConverter tester = null;
 	Logger log = Logger.getLogger(this.getClass());
 	protected void setUp() throws Exception {
 		tester = new LeadingSpacesConverter();