
1 // 2 // Copyright (c) 2007, Brian Frank and Andy Frank 3 // Licensed under the Academic Free License version 3.0 4 // 5 // History: 6 // 11 May 07 Brian Frank Creation 7 // 8 9 using compiler 10 using fandoc 11 12 ** 13 ** UriMapper is used to normalize fandoc URIs into hrefs to 14 ** their HTML file representation using relative URLs. 15 ** 16 class UriMapper : DocCompilerSupport 17 { 18 19 ////////////////////////////////////////////////////////////////////////// 20 // Constructor 21 ////////////////////////////////////////////////////////////////////////// 22 23 new make(DocCompiler compiler) 24 : super(compiler) 25 { 26 } 27 28 ////////////////////////////////////////////////////////////////////////// 29 // Access 30 ////////////////////////////////////////////////////////////////////////// 31 32 ** 33 ** Given a fandoc uri string, map it to a relative URL 34 ** to the resource's HTML. 35 ** 36 Uri map(Str fandocUri, Location loc) 37 { 38 this.fandocUri = fandocUri 39 this.loc = loc 40 this.frag = null 41 this.targetUri = null 42 this.targetIsCode = false 43 this.targetIsSlot = false 44 45 // if document internal fragment identifer then 46 // bail now before we do any work or use our cache 47 // which spans all the documents in the current pod 48 // reset working fields 49 if (fandocUri.startsWith("#")) 50 return targetUri = Uri.fromStr(fandocUri) 51 52 // if absolute then bail 53 if (fandocUri.startsWith("http:") || 54 fandocUri.startsWith("https:") || 55 fandocUri.startsWith("ftp:") || 56 fandocUri.startsWith("mailto:")) 57 return targetUri = Uri.fromStr(fandocUri) 58 59 // check the cache 60 cached := cache[fandocUri] 61 if (cached != null) 62 { 63 targetUri = cached.targetUri 64 targetIsCode = cached.targetIsCode 65 return targetUri 66 } 67 68 // strip off fragment if specified 69 pound := fandocUri.index("#") 70 if (pound != null) 71 { 72 // if URI is to frag within doc we are done 73 if (pound == 0) return targetUri = Uri.fromStr(fandocUri) 74 75 // split off fragment identifier 76 this.fandocUri = fandocUri[0...pound] 77 this.frag = fandocUri[pound+1..-1] 78 } 79 80 // map 81 try 82 { 83 if (fandocUri.contains("::")) 84 mapPod 85 else 86 mapTypeOrFile(compiler.pod, this.fandocUri) 87 88 // cache for next time 89 if (!targetIsSlot) 90 cache[fandocUri] = CachedUri.make(targetUri, targetIsCode) 91 } 92 catch (CompilerErr e) 93 { 94 targetUri = fandocUri.toUri 95 } 96 catch (Err e) 97 { 98 errReport(CompilerErr.make("Cannot map uri '$fandocUri'", loc, e)) 99 targetUri = fandocUri.toUri 100 } 101 102 // return result 103 return targetUri 104 } 105 106 ////////////////////////////////////////////////////////////////////////// 107 // Map 108 ////////////////////////////////////////////////////////////////////////// 109 110 private Void mapPod() 111 { 112 // lookup pod 113 colons := fandocUri.index("::") 114 podName := fandocUri[0...colons] 115 pod := Pod.find(podName, false) 116 if (pod == null) throw err("Unknown pod '$podName'", loc) 117 118 rest := fandocUri[colons+2..-1] 119 mapTypeOrFile(pod, rest) 120 } 121 122 private Void mapTypeOrFile(Pod pod, Str s) 123 { 124 typeName := s 125 Str rest := null 126 127 if (s == "index") 128 { 129 targetUri = toUri(pod, "index.html", frag) 130 return 131 } 132 133 dot := s.index(".") 134 if (dot != null) 135 { 136 typeName = s[0...dot] 137 rest = s[dot+1..-1] 138 } 139 else if (compiler.curType != null) 140 { 141 // if this string maps to a slot in the current 142 // type being processes - then slot name trumps 143 t := compiler.curType 144 slot := t.slot(s, false) 145 if (slot != null) 146 { 147 targetIsSlot = true 148 targetUri = toUri(slot.parent.pod, "${slot.parent.name}.html", slot.name) 149 return 150 } 151 } 152 153 // first try type in pod 154 t := pod.findType(typeName, false) 155 if (t != null) 156 { 157 mapSlot(t, rest) 158 return 159 } 160 161 // if dot ext, this must be a filename which we don't do yet 162 // TODO: eventually we need we need a standard mechanism 163 // to determine which resources to copy to the doc directory 164 // probably under some specific dir, which we can map 165 if (rest != null) 166 { 167 if (fandocUri.endsWith(".html")) 168 { 169 echo("WARNING: Need to fix unresolved fandoc uri bug: $fandocUri") 170 targetUri = fandocUri.toUri 171 } 172 else 173 { 174 throw err("Unresolved fandoc uri '$fandocUri'", loc) 175 } 176 } 177 178 // try to find fandoc file in pod 179 fandocFile := pod.files.find |File f->Bool| 180 { 181 return f.basename == typeName && f.ext == "fandoc" 182 } 183 184 if (fandocFile == null) 185 throw err("Unresolved fandoc uri '$fandocUri'", loc) 186 187 // if this a fragment identifier within the 188 // fandoc file then check that it exists 189 if (frag != null) 190 { 191 // ensure we've parsed the fandoc file into memory 192 // to create a table of all the fragment identifiers 193 if (fandocFrags[fandocUri] == null) 194 fandocFrags[fandocUri] = parseFandocFrags(fandocFile) 195 196 // check that the fragment exists 197 if (!fandocFrags[fandocUri].frags.containsKey(frag)) 198 throw err("Unknown fragment '#$frag' in '$fandocUri'", loc) 199 } 200 201 // we can now build our uri 202 targetUri = toUri(pod, "${fandocFile.basename}.html", frag) 203 } 204 205 private Void mapSlot(Type t, Str rest) 206 { 207 targetIsCode = true 208 Str slotFrag := null 209 210 // if rest it must be a slot name 211 if (rest != null) 212 { 213 slot := t.slot(rest, false) 214 if (slot == null) 215 throw err("Unresolved uri to slot '${t.qname}.$rest'", loc) 216 if (slot.parent != t) 217 throw err("Uri to inherited slot '${t.qname}.$rest' is declared on '$slot.parent'", loc) 218 slotFrag = slot.name 219 targetIsSlot = true 220 } 221 222 targetUri = toUri(t.pod, "${t.name}.html", slotFrag) 223 } 224 225 private Uri toUri(Pod pod, Str uri, Str frag) 226 { 227 if (pod != compiler.pod) uri = "../$pod.name/" + uri 228 if (frag != null) uri = uri + "#" + frag 229 return Uri.fromStr(uri) 230 } 231 232 ////////////////////////////////////////////////////////////////////////// 233 // FandocFrag Parse 234 ////////////////////////////////////////////////////////////////////////// 235 236 private FandocFrags parseFandocFrags(File f) 237 { 238 try 239 { 240 log.debug(" Parse fandoc frag [$fandocUri]") 241 docFrags := FandocFrags.make 242 f.readAllLines.each |Str line| 243 { 244 x := line.index("[#") 245 if (x != null) 246 { 247 y := line.index("]", x) 248 if (y != null) 249 { 250 frag := line[x+2...y] 251 docFrags.frags[frag] = frag 252 } 253 } 254 } 255 return docFrags 256 } 257 catch (Err e) 258 { 259 e.trace 260 throw err("Cannot parse fandoc file for fragments: $f", loc) 261 } 262 } 263 264 ////////////////////////////////////////////////////////////////////////// 265 // Fields 266 ////////////////////////////////////////////////////////////////////////// 267 268 Str fandocUri 269 Str frag 270 Location loc 271 Uri targetUri 272 Bool targetIsCode 273 Bool targetIsSlot 274 Str:CachedUri cache := Str:CachedUri[:] 275 Str:FandocFrags fandocFrags := Str:FandocFrags[:] 276 } 277 278 internal class CachedUri 279 { 280 new make(Uri uri, Bool isCode) 281 { 282 targetUri = uri 283 targetIsCode = isCode 284 } 285 286 Uri targetUri 287 Bool targetIsCode 288 } 289 290 internal class FandocFrags 291 { 292 Str:Str frags := Str:Str[:] 293 } 294