@@ -318,27 +318,21 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
318318
319319if (! settings.docsourceurl.isDefault)
320320 inSource.map {case (file, line)=>
321- // file path is relative to source root (-sourcepath)
322- val src = Paths .get(settings.sourcepath.value).toUri
323- val path = file.file.toPath.toUri
324- val filePathExt = src.relativize(path)
325- val rawPath : String = filePathExt.getRawPath
321+ val filePathExt = {
322+ // file path is relative to source root (-sourcepath); use an absolute path otherwise
323+ val sp = settings.sourcepath.value
324+ val fileUri = file.file.toPath.toUri
325+ if (sp.isEmpty) fileUri.getRawPath
326+ else Paths .get(sp).toUri.relativize(fileUri).getRawPath
327+ }
326328val (filePath, fileExt)=
327- rawPath .lastIndexOf('.' )match {
328- case - 1 => (rawPath ," " )
329- case i=> rawPath .splitAt(i)
329+ filePathExt .lastIndexOf('.' )match {
330+ case - 1 => (filePathExt ," " )
331+ case i=> filePathExt .splitAt(i)
330332 }
331333val tplOwner = this .inTemplate.qualifiedName
332334val tplName = this .name
333- def substitute (name :String ): String = namematch {
334- case FILE_PATH => filePath
335- case FILE_EXT => fileExt
336- case FILE_PATH_EXT => filePathExt.toString
337- case FILE_LINE => line.toString
338- case TPL_OWNER => tplOwner
339- case TPL_NAME => tplName
340- }
341- val patchedString = tokens.replaceAllIn(settings.docsourceurl.value, m=> quoteReplacement(substitute(m.group(1 ))) )
335+ val patchedString = expandUrl(settings.docsourceurl.value, filePath, fileExt, filePathExt, line, tplOwner, tplName)
342336new URI (patchedString).toURL
343337 }
344338else None
@@ -1061,11 +1055,38 @@ object ModelFactory {
10611055val defaultGroupDesc = None
10621056val defaultGroupPriority = 1000
10631057
1064- val tokens = raw " €\{( $FILE_PATH| $FILE_EXT | $FILE_PATH_EXT | $FILE_LINE| $TPL_OWNER| $TPL_NAME)\} " .r
1058+ val tokens = raw " (?:(.?) €\{( $FILE_PATH| $FILE_PATH_EXT )\}|\.?€\{( $FILE_EXT )\}|€\{( $FILE_LINE| $TPL_OWNER| $TPL_NAME)\}) " .r
10651059final val FILE_PATH = " FILE_PATH"
10661060final val FILE_EXT = " FILE_EXT"
10671061final val FILE_PATH_EXT = " FILE_PATH_EXT"
10681062final val FILE_LINE = " FILE_LINE"
10691063final val TPL_OWNER = " TPL_OWNER"
10701064final val TPL_NAME = " TPL_NAME"
1065+
1066+ val WordChar = raw " \w " .r
1067+
1068+ def expandUrl (urlTemplate :String ,filePath :String ,fileExt :String ,filePathExt :String ,line :Int ,tplOwner :String ,tplName :String ): String = {
1069+ val absolute = filePath.startsWith(" /" )
1070+
1071+ def subst (groups :List [String ]): String = {
1072+ // If a relative path follows a word character, insert a `/`
1073+ def sep : String = groups.headmatch {
1074+ case null => " "
1075+ case " /" if absolute=> " "
1076+ case c@ WordChar ()if ! absolute=> c+ " /"
1077+ case c=> c
1078+ }
1079+
1080+ groups.tail.find(_!= null ).getmatch {
1081+ case FILE_PATH => s " $sep$filePath"
1082+ case FILE_EXT => fileExt
1083+ case FILE_PATH_EXT => s " $sep$filePathExt"
1084+ case FILE_LINE => line.toString
1085+ case TPL_OWNER => tplOwner
1086+ case TPL_NAME => tplName
1087+ }
1088+ }
1089+
1090+ tokens.replaceAllIn(urlTemplate, m=> quoteReplacement(subst(m.subgroups)))
1091+ }
10711092}