diff --git a/AstConv/HtmlStruct.fs b/AstConv/HtmlStruct.fs
index aa8e39d..babcb10 100644
--- a/AstConv/HtmlStruct.fs
+++ b/AstConv/HtmlStruct.fs
@@ -28,11 +28,11 @@ open System.IO
class
let mutable root: string = ""
member this.pageURL(): string = $"{root}{name}.html"
- member this.setPageRoot(path: string) =
- if path.EndsWith "/" then
- root <- path
+ member this.setPageRoot(path: Uri) =
+ if path.AbsolutePath.EndsWith "/" then
+ root <- path.AbsolutePath
else
- root <- path+"/"
+ root <- path.AbsolutePath + "/"
end
/// 内容定义元素 =================================================================================
@@ -759,4 +759,449 @@ open System.IO
doc.WriteTo(writer)
writer.Flush()
out.ToString()
+ end
+
+ type IndexPage(name: string, childs: Present.IDomUnit list) =
+ class
+ inherit Present.PageAccess("index")
+ new(name) = IndexPage(name, [])
+
+ member this.append(arg: Present.IDomUnit list) =
+ IndexPage(name, childs @ arg)
+
+ interface Present.IDomUnit with
+ member this.name(): string = name
+ member this.object(): AstImport.AstObject =
+ raise (System.NotImplementedException())
+
+ interface PageText with
+ member this.bindPage(): obj = this
+ member this.getHtmlText(): string =
+ let doc = XmlDocument()
+
+ let doc_type = doc.CreateDocumentType("html", null, null, null)
+ doc.AppendChild(doc_type) |> ignore
+
+ let html = doc.CreateElement("html")
+ let head = doc.CreateElement("head")
+ let charset = doc.CreateElement("meta")
+ charset.SetAttribute("charset", "UTF-8")
+ head.AppendChild(charset) |> ignore
+
+ let refresh = doc.CreateElement("meta")
+ refresh.SetAttribute("http-equiv", "refresh")
+ refresh.SetAttribute("content", "5")
+ head.AppendChild(refresh) |> ignore
+
+ let title = doc.CreateElement("title")
+ title.AppendChild(doc.CreateTextNode(name)) |> ignore
+ head.AppendChild(title) |> ignore
+
+ let style = doc.CreateElement("style")
+ style.AppendChild(doc.CreateTextNode("""body{
+ background-color:lightgray;
+ }
+
+ h1 {
+ margin-left:300px;
+ margin-top: 50px;
+ margin-bottom: 30px;
+ }
+ div.outline {
+ max-width:90%;
+ #outline:green dotted thick;
+ margin:auto;
+ display:flex;
+ background-color:#f0f0f0;
+ }
+ div#left-nav{
+ min-width:30%;
+ max-width:30%;
+
+ # border:1px solid red;
+ # outline:green dotted thick;
+ overflow:clip;
+ padding:20px;
+ background-image: linear-gradient(
+ to right,
+ #e0e0e0 0%,
+ #ffffff 100%
+ );
+ }
+ div#left-nav p {
+ word-wrap:break-word;
+ text-align:right;
+ }
+
+ div.content {
+ padding: 5px;
+ padding-left: 20px;
+ max-width: 90%;
+ outline: #e0e0e0 solid 1px;
+ box-shadow: 2px 2px 2px gray;
+ }
+ .content p {
+ word-wrap:break-word;
+ }
+ """)) |> ignore
+ head.AppendChild(style) |> ignore
+
+ html.AppendChild(head) |> ignore
+ doc.AppendChild(html) |> ignore
+
+ let body = doc.CreateElement("body")
+ html.AppendChild(body) |> ignore
+
+ let h1 = doc.CreateElement("h1")
+ h1.AppendChild(doc.CreateTextNode(name)) |> ignore
+ body.AppendChild(h1) |> ignore
+
+ let div_outline = doc.CreateElement("div")
+ div_outline.SetAttribute("class", "outline")
+ body.AppendChild(div_outline) |> ignore
+
+ let div_nav = doc.CreateElement("div")
+ div_nav.SetAttribute("id", "left-nav")
+ div_outline.AppendChild(div_nav) |> ignore
+ this.initNavigate(div_nav) |> ignore
+
+ let div_content = doc.CreateElement("div")
+ div_content.SetAttribute("id", "content")
+ div_content.SetAttribute("class", "content")
+ div_outline.AppendChild(div_content) |> ignore
+ this.contentAppend(div_content, childs)
+
+ let out = new StringWriter()
+ let writer = new XmlTextWriter(out)
+ writer.Formatting = Formatting.Indented |> ignore
+ doc.WriteTo(writer)
+ writer.Flush()
+ out.ToString()
+
+ member this.initNavigate(nav: XmlElement): unit =
+ let vdoc = nav.OwnerDocument
+ let p1 = vdoc.CreateElement("p")
+ let a1 = vdoc.CreateElement("a")
+ a1.SetAttribute("href", "#volume");
+ a1.AppendChild(vdoc.CreateTextNode("卷宗组织")) |> ignore
+ p1.AppendChild(a1) |> ignore
+ nav.AppendChild(p1) |> ignore
+
+ let p2 = vdoc.CreateElement("p")
+ let a2 = vdoc.CreateElement("a")
+ a2.SetAttribute("href", "#story");
+ a2.AppendChild(vdoc.CreateTextNode("故事脉络")) |> ignore
+ p2.AppendChild(a2) |> ignore
+ nav.AppendChild(p2) |> ignore
+
+ let p4 = vdoc.CreateElement("p")
+ let a4 = vdoc.CreateElement("a")
+ a4.SetAttribute("href", "#story-graph");
+ a4.AppendChild(vdoc.CreateTextNode("脉络图示")) |> ignore
+ p4.AppendChild(a4) |> ignore
+ nav.AppendChild(p4) |> ignore
+
+ let p3 = vdoc.CreateElement("p")
+ let a3 = vdoc.CreateElement("a")
+ a3.SetAttribute("href", "#volume-graph");
+ a3.AppendChild(vdoc.CreateTextNode("卷宗图示")) |> ignore
+ p3.AppendChild(a3) |> ignore
+ nav.AppendChild(p3) |> ignore
+
+ member this.contentAppend(content: XmlElement, childs: Present.IDomUnit list): unit =
+ let vdoc = content.OwnerDocument
+ // volume汇总
+ let div_volume = vdoc.CreateElement("div")
+ div_volume.SetAttribute("data-type", "volume")
+ div_volume.SetAttribute("class", "content")
+ let volume_title = vdoc.CreateElement("h3")
+ volume_title.SetAttribute("id", "volume")
+ volume_title.AppendChild(vdoc.CreateTextNode("卷宗组织")) |> ignore
+ div_volume.AppendChild(volume_title) |> ignore
+ content.AppendChild(div_volume) |> ignore
+ for p in childs do
+ match p with
+ | :? Present.VolumePage ->
+ let div_page = vdoc.CreateElement("div")
+ let page_a = vdoc.CreateElement("a")
+ let page_bind = p :?> Present.PageAccess
+ page_a.SetAttribute("href", page_bind.pageURL())
+ page_a.AppendChild(vdoc.CreateTextNode(p.name())) |> ignore
+ div_page.AppendChild(page_a) |> ignore
+ div_volume.AppendChild(div_page) |> ignore
+ | _ -> ()
+
+ // story汇总
+ let div_story = vdoc.CreateElement("div")
+ div_story.SetAttribute("data-type", "story")
+ div_story.SetAttribute("class", "content")
+ let story_title = vdoc.CreateElement("h3")
+ story_title.SetAttribute("id", "story")
+ story_title.AppendChild(vdoc.CreateTextNode("故事脉络")) |> ignore
+ div_story.AppendChild(story_title) |> ignore
+ content.AppendChild(div_story) |> ignore
+ for p in childs do
+ match p with
+ | :? Present.StoryPage ->
+ let div_page = vdoc.CreateElement("div")
+ let page_a = vdoc.CreateElement("a")
+ let page_bind = p :?> Present.PageAccess
+ page_a.SetAttribute("href", page_bind.pageURL())
+ page_a.AppendChild(vdoc.CreateTextNode(p.name())) |> ignore
+ div_page.AppendChild(page_a) |> ignore
+ div_story.AppendChild(div_page) |> ignore
+ | _ -> ()
+
+ // storylines 网络图
+ let div_story_svg = vdoc.CreateElement("div")
+ div_story_svg.SetAttribute("data-type", "story_svg")
+ div_story_svg.SetAttribute("id", "story-graph")
+ let story_h3 = vdoc.CreateElement("h3")
+ story_h3.AppendChild(vdoc.CreateTextNode("脉络图示")) |> ignore
+ div_story_svg.AppendChild(story_h3) |> ignore
+ let image = vdoc.CreateElement("img")
+ image.SetAttribute("src", "./storys_display.svg")
+ div_story_svg.AppendChild(image) |> ignore
+ content.AppendChild(div_story_svg) |> ignore
+
+ // volumes 引用图
+ let div_volume_svg = vdoc.CreateElement("div")
+ div_volume_svg.SetAttribute("data-type", "volume_svg")
+ div_volume_svg.SetAttribute("id", "volume-graph")
+ let volume_h3 = vdoc.CreateElement("h3")
+ volume_h3.AppendChild(vdoc.CreateTextNode("卷宗图示")) |> ignore
+ div_volume_svg.AppendChild(volume_h3) |> ignore
+ let image2 = vdoc.CreateElement("img")
+ image2.SetAttribute("src", "./volume_display.svg")
+ div_volume_svg.AppendChild(image2) |> ignore
+ content.AppendChild(div_volume_svg) |> ignore
+ end
+
+
+
+
+ type StorylineGraphMake(gname:string, items: Present.PageAccess list) =
+ class
+ let lines = items |> List.filter(fun v-> v :? Present.StoryPage)
+ |> List.map(fun v->v:?>Present.StoryPage)
+ |> List.map(fun s->s :> Present.IDomUnit)
+ |> List.map(fun s->s.object() :?> AstImport.StoryDef)
+
+ member private this.node_collect(objs: AstImport.AstObject list) =
+ match objs with
+ | [] -> []
+ | _ ->
+ match objs.Head with
+ | :? AstImport.StoryDef as story ->
+ let list_a = this.node_collect(story.children())
+ (list_a|> List.map(fun (_, b, c) -> Some(story), b, c))@this.node_collect(objs.Tail)
+ | :? AstImport.SliceDef as slice ->
+ let list_a = this.node_collect(slice.children())
+ (list_a|> List.map(fun (_, _, c) -> None, Some(slice), c))@this.node_collect(objs.Tail)
+ | :? AstImport.PointDef as point ->
+ (None, None, point :> AstImport.AstObject)::this.node_collect(objs.Tail)
+ | :? AstImport.PointRef as refer ->
+ (None, None, refer)::this.node_collect(objs.Tail)
+ | _ -> this.node_collect(objs.Tail)
+
+ member this.getGraphCode(): string =
+ let node_about = this.node_collect(lines|> List.map(fun d -> d :> AstImport.AstObject))
+
+ // 获取节点字典
+ let node_map = node_about|> List.filter(fun (_, _, c) -> c :? AstImport.PointDef)
+ |> List.map(
+ fun (a, b, c) ->
+ $"{a.Value.name()}&{b.Value.name()}&{(c:?>AstImport.PointDef).name()}", c.address()
+ )
+
+
+ // 节点声明
+ let story_decl = lines |> List.map(fun story ->
+ $"""node_{story.address()}[label="{story.name()}" shape="cds"]""")
+ |> List.reduce(fun a b -> a + "\n" + b)
+ let points_decl = node_about |> List.map(fun (_, _, point) ->
+ match point with
+ | :? AstImport.PointDef as def->
+ $"""node_{point.address()}[label="{def.name()}" shape="rect"]"""
+ | :? AstImport.PointRef as ref ->
+ $"""node_{point.address()}[label="{ref.pointRef()}" style="dotted"]"""
+ | _ -> failwith "mismatch"
+ )
+ |> List.reduce(fun a b -> a + "\n" + b)
+ let nodes_decl = story_decl + points_decl
+
+
+ // 提取所有情节声明
+ let slice_nodes = node_about|> List.map(fun (a, b, _)->a.Value,b.Value)|> List.distinctBy(fun (_, b) -> b.address())
+ let rec get_slice_decl = fun (slices_t: (AstImport.StoryDef*AstImport.SliceDef) list)->
+ match slices_t with
+ | [] -> []
+ | _ ->
+ let story, slice = slices_t.Head
+ let slice_childs = node_about
+ |> List.filter(
+ fun (_, b, _) ->
+ slice.address() = b.Value.address()
+ )
+ |> List.map(fun (_, _, n) -> $"node_{n.address()}")
+ |> List.reduce(fun a b -> $"{a}->{b}")
+
+ let slice_def = $"""subgraph cluster_{slice.address()}{{ label="{story.name()}::{slice.name()}" {slice_childs} }}"""
+ slice_def::get_slice_decl(slices_t.Tail)
+ let slice_relates = get_slice_decl(slice_nodes)|> List.reduce(fun a b -> a + "\n" + b)
+
+
+ // 构建引用指向
+ let refer_nodes = node_about|> List.filter(fun (_, _, n) -> n:? AstImport.PointRef)
+ |> List.map(fun (_, _, n) -> n :?> AstImport.PointRef)
+ let rec get_refer_decl = fun(list_def: (string*string)list) ->
+ match list_def with
+ | [] -> []
+ | _ ->
+ let node_name,node_addr = list_def.Head
+ let refers_about = refer_nodes|> List.filter(fun d -> node_name = $"{d.storyRef()}&{d.sliceRef()}&{d.pointRef()}")
+ match refers_about with
+ | [] -> get_refer_decl(list_def.Tail)
+ | _ ->
+ let refer_arrows = refers_about|> List.map(fun d -> $"node_{d.address()}->node_{node_addr}")
+ |> List.reduce(fun a b -> a + "\n" + b)
+ refer_arrows::get_refer_decl(list_def.Tail)
+ let refer_arrows = get_refer_decl(node_map)|> List.reduce(fun a b -> a + "\n" + b)
+
+
+ // 串联情节
+ let rec slice_combine = fun(items: AstImport.AstObject list) ->
+ match items with
+ | [] -> []
+ | _ ->
+ match items.Head with
+ | :? AstImport.SliceDef as defs ->
+ let points = defs.children()|> List.filter(fun d -> not(d :? AstImport.TextItem))
+ match points with
+ | [] -> []
+ | _ ->
+ (points.Head, points.Item(points.Length-1))::slice_combine(items.Tail)
+ | :? AstImport.StoryDef as defs ->
+ slice_combine(defs.children())
+ | _ -> slice_combine(items.Tail)
+ let story_arrows = lines |> List.map(fun story ->
+ let slice_ends = slice_combine([story])
+ match slice_ends with
+ | [] -> ""
+ | _ ->
+ let arrow_link = slice_ends |> List.map(fun (a,b) -> $"node_{a.address()}&&&&node_{b.address()}")
+ |> List.reduce(fun a b -> $"{a}->{b}")
+ $"""node_{story.address()}->{arrow_link.Replace("&&&&", "\n")}"""
+ )
+ |> List.reduce(fun a b -> a + "\n" + b)
+
+ $"""digraph node_relates{{
+ rankdir=LR
+ label="{gname}"
+ {nodes_decl}
+ {slice_relates}
+ {refer_arrows}
+ {story_arrows}
+ }}"""
+ end
+
+ type VolumeGraphMake(gname: string, story_volume_set: Present.PageAccess list) =
+ class
+ member private this.node_collect(objs: AstImport.AstObject list) =
+ match objs with
+ | [] -> []
+ | _ ->
+ match objs.Head with
+ | :? AstImport.StoryDef as story ->
+ let list = this.node_collect(story.children())
+ (list|> List.map(fun (_, b, c) -> Some(story :> AstImport.AstObject), b, c))@this.node_collect(objs.Tail)
+ | :? AstImport.SliceDef as slice ->
+ let list = this.node_collect(slice.children())
+ (list|> List.map(fun (_, _, c) -> None, Some(slice :> AstImport.AstObject), c))@this.node_collect(objs.Tail)
+ | :? AstImport.VolumeDef as volume ->
+ let list = this.node_collect(volume.children())
+ (list|> List.map(fun (_, b, c) -> Some(volume), b, c))@this.node_collect(objs.Tail)
+ | :? AstImport.ArticleDef as article ->
+ let list = this.node_collect(article.children())
+ (list|> List.map(fun (_, _, c) -> None, Some(article), c))@this.node_collect(objs.Tail)
+ | :? AstImport.PointDef as point ->
+ (None, None, point :> AstImport.AstObject)::this.node_collect(objs.Tail)
+ | :? AstImport.PointRef as refer ->
+ (None, None, refer)::this.node_collect(objs.Tail)
+ | _ -> this.node_collect(objs.Tail)
+
+ member this.getGraphCode(): string =
+ let storys = story_volume_set
+ |> List.filter(fun dt -> dt :? Present.StoryPage)
+ |> List.map(fun d -> d :?> Present.StoryPage)
+ |> List.map(fun d -> d :> Present.IDomUnit)
+ |> List.map(fun d -> d.object())
+ let volumes= story_volume_set
+ |> List.filter(fun dt -> dt :? Present.VolumePage)
+ |> List.map(fun dt -> dt :?> Present.VolumePage)
+ |> List.map(fun d -> d :> Present.IDomUnit)
+ |> List.map(fun d -> d.object())
+
+ let rec point_code_generate(list: AstImport.AstObject list) =
+ match list with
+ | [] -> []
+ | _ ->
+ let item_current = list.Head
+ match item_current with
+ | :? AstImport.StoryDef as story ->
+ let subcluster = point_code_generate(story.children())
+ let clusters_desc = if subcluster.Length > 0 then subcluster|> List.reduce(fun a b -> a + "\n" + b) else ""
+ let story_desc = $""" subgraph cluster_{story.address()} {{ label="{story.name()}" {clusters_desc} }} """
+ story_desc::point_code_generate(list.Tail)
+ | :? AstImport.SliceDef as slice ->
+ let node_exists = point_code_generate(slice.children())
+ let nodes_desc = if node_exists.Length > 0 then node_exists|> List.reduce(fun a b -> a+"\n"+b) else ""
+ let slice_desc = $""" subgraph cluster_{slice.address()} {{ label="{slice.name()}" {nodes_desc} }} """
+ slice_desc::point_code_generate(list.Tail)
+ | :? AstImport.PointDef as point ->
+ let node_desc = $""" node_{point.address()}[label="{point.name()}" shape="rect" style="diagonals"] """
+ node_desc::point_code_generate(list.Tail)
+ | :? AstImport.PointRef as refer ->
+ let refer_desc = $""" node_{refer.address()}[label="{refer.pointRef()}" style="dotted"] """
+ refer_desc::point_code_generate(list.Tail)
+ | :? AstImport.ArticleDef as article ->
+ let refer_descx = point_code_generate(article.children())
+ let nodes_desc = if refer_descx.Length > 0 then refer_descx|> List.reduce(fun a b -> a+"\n"+b) else ""
+ let article_desc = $""" subgraph cluster_{article.address()} {{ label="{article.name()}" {nodes_desc} }} """
+ article_desc::point_code_generate(list.Tail)
+ | :? AstImport.VolumeDef as volume ->
+ let subclusters = point_code_generate(volume.children())
+ let clusters_desc = if subclusters.Length > 0 then subclusters|> List.reduce(fun a b -> a + "\n" + b) else ""
+ let volume_desc = $""" subgraph cluster_{volume.address()} {{ label="{volume.name()}" {clusters_desc} }} """
+ volume_desc::point_code_generate(list.Tail)
+ | _ -> point_code_generate(list.Tail)
+
+ let clusters_desc = (point_code_generate(storys)@point_code_generate(volumes))|> List.reduce(fun a b -> a + "\n" + b)
+
+ let points_about = this.node_collect(storys @ volumes)
+ let point_map = points_about|> List.filter(fun (_, _, d) -> d :? AstImport.PointDef)
+ |> List.map(fun (a, b, c) ->
+ let story_def = a.Value :?> AstImport.StoryDef
+ let slice_def = b.Value :?> AstImport.SliceDef
+ $"{story_def.name()}&{slice_def.name()}&{(c :?> AstImport.PointDef).name()}", c.address()
+ )
+ let rec refers_link_assemble(nodes: (string*string)list) =
+ match nodes with
+ | [] -> []
+ | _ ->
+ let node_sig, address = nodes.Head
+ let referx = points_about|> List.filter(fun (_, _, n) -> n :? AstImport.PointRef)
+ |> List.map(fun (_, _, c) -> c :?> AstImport.PointRef)
+ |> List.filter(fun n ->
+ let sig_ref = $"{n.storyRef()}&{n.sliceRef()}&{n.pointRef()}"
+ sig_ref = node_sig
+ )
+ let code_curr = referx |> List.map(fun x-> $"node_{x.address()}--node_{address}")
+ code_curr@refers_link_assemble(nodes.Tail)
+
+ let temps = refers_link_assemble(point_map)
+ let refer_arrows = refers_link_assemble(point_map)|> List.reduce(fun a b -> a + "\n" + b)
+
+ $"""graph{{ label="{gname}" {clusters_desc} {refer_arrows} }}"""
+
end
\ No newline at end of file
diff --git a/AstConv/Program.fs b/AstConv/Program.fs
index 1ad452e..97e7f20 100644
--- a/AstConv/Program.fs
+++ b/AstConv/Program.fs
@@ -5,13 +5,29 @@ open HtmlStruct.Assemble
open HtmlStruct.Content
open HtmlStruct.Present
open System.IO
+open System.Text
+open System
+open System.Diagnostics
+let args = System.Environment.GetCommandLineArgs()|> Array.toList
+if args.Length <> 5 then
+ failwith "程序调用参数错误:AstConv.exe -file xast文件路径 -odir 输出文件夹路径"
+
+let file_in, dir_o = match args.Item(1), args.Item(3) with
+ | "-file","-odir" ->
+ if not(FileInfo(args.Item 2).Exists) then
+ failwith "指定xast文件不存在"
+ if not(DirectoryInfo(args.Item 4).Exists) then
+ failwith "指定输出文件夹不存在"
+ FileInfo(args.Item 2), DirectoryInfo(args.Item 4)
+ | _ -> failwith "程序调用参数错误:AstConv.exe -file xast文件路径 -odir 输出文件夹路径"
+
+let out_dir = Uri(dir_o.FullName)
let doc = XmlDocument()
-doc.Load("E:/storyline.xast")
+doc.Load(file_in.FullName)
let prog = Program.GenerateFrom(doc)
-
let entry = AstVisitEntry(prog)
let visitor = UnitGenerate(prog)
entry.visitWith(visitor) |> ignore
@@ -20,7 +36,7 @@ let volume_pages = volume_page_assemble(visitor.contents()) |> List.map List.map (fun x->x)
let point_pages = point_page_assemble(volume_pages @ story_pages)
-volume_pages @ story_pages @ point_pages |> List.iter (fun it -> (it:?>PageAccess).setPageRoot("E:"))
+volume_pages @ story_pages @ point_pages |> List.iter (fun it -> (it:?>PageAccess).setPageRoot(out_dir))
let makers = volume_pages @ story_pages @ point_pages
|> List.map(fun page_unit ->
match page_unit with
@@ -33,6 +49,26 @@ let makers = volume_pages @ story_pages @ point_pages
| _ -> failwith ""
)
+let index_page = IndexPage("汇总页面", volume_pages @ story_pages @ point_pages)
+index_page.setPageRoot(out_dir)
+let content = (index_page :> PageText).getHtmlText()
+let href = index_page.pageURL()
+File.WriteAllLines(href, [content])
for refs in makers do
let file_path = (refs.bindPage() :?> PageAccess).pageURL()
- File.WriteAllLines(file_path, [refs.getHtmlText()])
\ No newline at end of file
+ File.WriteAllLines(file_path, [refs.getHtmlText()])
+
+let graph = StorylineGraphMake("故事线网络", story_pages|> List.map(fun x -> x :?> PageAccess))
+let graph_code = graph.getGraphCode()
+
+let stream = new StreamWriter(Path.Combine(dir_o.FullName, "storys_display.dot"), false)
+stream.Write(graph_code)
+stream.Flush()
+Process.Start("dot", $"""-Tsvg -o{Path.Combine(dir_o.FullName, "storys_display.svg")} {Path.Combine(dir_o.FullName, "storys_display.dot")}""") |> ignore
+
+let graph2 = VolumeGraphMake("卷章引用网络", (volume_pages@story_pages)|> List.map(fun d -> d :?> PageAccess))
+let graph2_code = graph2.getGraphCode()
+let stream2 = new StreamWriter(Path.Combine(dir_o.FullName, "volume_display.dot"), false)
+stream2.Write(graph2_code)
+stream2.Flush()
+Process.Start("dot", $"""-Tsvg -o{Path.Combine(dir_o.FullName, "volume_display.svg")} {Path.Combine(dir_o.FullName, "volume_display.dot")}""") |> ignore
diff --git a/AstConv/Properties/launchSettings.json b/AstConv/Properties/launchSettings.json
new file mode 100644
index 0000000..79fd873
--- /dev/null
+++ b/AstConv/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "AstConv": {
+ "commandName": "Project",
+ "commandLineArgs": "-file \"E:/storyline.xast\" -odir \"E:/diro\""
+ }
+ }
+}
\ No newline at end of file