diff --git a/AstConv/AstConv.fsproj b/AstConv/AstConv.fsproj index 303d6bd..a19e1de 100644 --- a/AstConv/AstConv.fsproj +++ b/AstConv/AstConv.fsproj @@ -8,6 +8,7 @@ + diff --git a/AstConv/AstImport.fs b/AstConv/AstImport.fs index 4fd3e08..6891cdd 100644 --- a/AstConv/AstImport.fs +++ b/AstConv/AstImport.fs @@ -5,11 +5,19 @@ open System.Linq module AstImport = /// 带有地址的对象 - type AstObject() = + type AstObject(bind: XmlElement) = class + member this.astXML() = bind + member this.rows() = + let xmle = bind.SelectSingleNode("tokens/token") :?> XmlElement + int(xmle.GetAttribute "row") + abstract member address:unit -> string default this.address():string = "mem_" + string(this.GetHashCode ()) + abstract member filePath:unit -> string + default this.filePath():string = "" + abstract member members:unit -> AstObject list default this.members(): AstObject list = [] end @@ -34,7 +42,7 @@ open System.Linq /// TextWord type TextItem(bind: XmlElement) = class - inherit AstObject() + inherit AstObject(bind) member this.content():string = bind.GetAttribute "text" @@ -54,7 +62,7 @@ open System.Linq /// 引用节点 type FragmentRef(bind: XmlElement, texts: TextItem list) = class - inherit AstObject() + inherit AstObject(bind) new(bind: XmlElement) = FragmentRef(bind, []) member this.sliceRef() = @@ -89,7 +97,7 @@ open System.Linq /// 情节节点 type FragmentSlice(bind: XmlElement, objs: AstObject list) = class - inherit AstObject() + inherit AstObject(bind) new(bind: XmlElement) = FragmentSlice(bind, []) member this.name() = @@ -134,7 +142,7 @@ open System.Linq /// 故事节点 type StoryDef(bind: XmlElement, objs: AstObject list) = class - inherit AstObject() + inherit AstObject(bind) new(bind: XmlElement) = StoryDef(bind, []) member this.name() = @@ -150,6 +158,8 @@ open System.Linq override this.members(): AstObject list = objs override this.address (): string = bind.GetAttribute "address" + override this.filePath (): string = + bind.GetAttribute "file-path" static member GenerateFromChildSibling(child: XmlNode): StoryChildType list = match child with @@ -181,7 +191,7 @@ open System.Linq /// 章节节点 type ArticleDef(bind: XmlElement, objs: AstObject list) = class - inherit AstObject() + inherit AstObject(bind) new(bind: XmlElement) = ArticleDef(bind, []) member this.name() = @@ -226,7 +236,7 @@ open System.Linq /// 卷宗节点 type VolumeDef(bind: XmlElement, objs: AstObject list) = class - inherit AstObject() + inherit AstObject(bind) new(bind: XmlElement) = VolumeDef(bind, []) member this.name() = @@ -244,6 +254,8 @@ open System.Linq override this.members(): AstObject list = objs override this.address (): string = bind.GetAttribute "address" + override this.filePath (): string = + bind.GetAttribute "file-path" static member GenerateFromChildSibling(child: XmlNode): VolumeChildType list = match child with @@ -270,11 +282,21 @@ open System.Linq end - type ProgramChildType = |Volume of VolumeDef |Story of StoryDef + type RankDef(bind: XmlElement) = + class + inherit AstObject(bind) + + member this.sort():int32 = + int32(bind.GetAttribute "rank") + override this.filePath (): string = + bind.GetAttribute "doc-path" + end + + type ProgramChildType = |Volume of VolumeDef |Story of StoryDef |Rank of RankDef /// 程序节点 type Program(bind: XmlElement, objs: AstObject list) = class - inherit AstObject() + inherit AstObject(bind) new(bind: XmlElement) = Program(bind, []) member this.time() = @@ -285,6 +307,7 @@ open System.Linq match it with |Story s -> s :> AstObject |Volume v -> v :> AstObject + |Rank r -> r:> AstObject ) Program(bind, objs @items) @@ -297,6 +320,7 @@ open System.Linq let d = match child.Name with | "volume" -> Some(Volume(VolumeDef.GenerateVolumeDef(child).Value)) | "story" -> Some(Story(StoryDef.GenerateStoryDef(child).Value)) + | "rank" -> Some(Rank(RankDef(child :?> XmlElement))) | _ -> None match d with |Some value -> value::Program.GenerateFromChildSibling(child.NextSibling) @@ -309,6 +333,7 @@ open System.Linq let conv data = match data with |Volume d -> d:>AstObject |Story d -> d :> AstObject + |Rank d -> d:> AstObject Program(ast, mbrs |> List.map conv) end diff --git a/AstConv/FmtStruct.fs b/AstConv/FmtStruct.fs new file mode 100644 index 0000000..75abfe2 --- /dev/null +++ b/AstConv/FmtStruct.fs @@ -0,0 +1,222 @@ +namespace FmtStruct + +open AstAccess +open HtmlStruct + + module FmtEntry = + // rank反序列化 + let rank_fmt(node: AstImport.AstObject): string = + let domx = node :?> AstImport.RankDef + $"#排序 {domx.sort()}\n" + + // 提取相同的类型元素 + let rec same_collect<'T when 'T:>Present.IDomUnit> + (nodes: Present.IDomUnit list): Present.IDomUnit list * Present.IDomUnit list = + + if nodes.Length = 0 then + [], [] + else + match nodes.Head with + | :? 'T -> + let tn, rest = same_collect<'T>(nodes.Tail) + nodes.Head::tn, rest + | _ -> [], nodes + + + // 分类两种不同的类型元素 + let rec same_fold<'T1,'T2 when 'T1:> Present.IDomUnit and 'T2:> Present.IDomUnit> + (nodes: Present.IDomUnit list): Present.IDomUnit list list = + let first_list, rest_0 = same_collect<'T1>(nodes) + let second_list, rest_1 = same_collect<'T2>(rest_0) + + let list = match rest_1 with + |[] -> first_list::[second_list] + | _ -> first_list::[second_list]@same_fold<'T1, 'T2>(rest_1) + list |> List.filter(fun slist -> slist.Length > 0) + + // 字符串格式化 + let rec text_fmt(prefix:string)(line_prev: int32)(nodes: Content.TextContent list): string = + match nodes with + | [] -> "" + | _ -> + let obj_text = (nodes.Head :> Present.IDomUnit).object() + let this_text = (obj_text :?> AstImport.TextItem).content() + if obj_text.rows() = line_prev then + " " + this_text + text_fmt prefix line_prev nodes.Tail + else + $"\n{prefix}" + this_text + text_fmt prefix (obj_text.rows()) nodes.Tail + + + // 格式化Refer:Node + let slice_ref_fmt(prefix:string)(node: Content.FragmentRefer): string = + let objref = (node:> Present.IDomUnit).object():?> AstImport.FragmentRef + + let childs = (node:> Present.IContainer).children() + let text_childs, rest = same_collect(childs) + assert(rest.Length = 0) + + let text_type_childs = text_childs|> List.map(fun n-> n:?> Content.TextContent) + let sections = text_fmt (prefix+" ") (objref.rows()) text_type_childs + + $"{prefix}{{@情节 {objref.storyRef()}&{objref.sliceRef()}{sections}}}" + + + // 格式化Slice:Node + let slice_def_fmt(prefix:string)(node: Content.FragmentSlice): string = + let obj_def = (node:> Present.IDomUnit).object():?> AstImport.FragmentSlice + + let childs = (node:> Present.IContainer).children() + let same_groups_set = same_fold(childs) + let contents = same_groups_set |> List.map(fun same_set -> + if same_set.Length = 0 then + "" + else + match same_set.Head with + | :? Content.FragmentRefer -> + let strx = same_set|> List.map(fun n -> + slice_ref_fmt (prefix+" ") (n:?> Content.FragmentRefer) + ) + strx |> List.reduce(fun a b -> a + "\n" + b) + | :? Content.TextContent -> + let typed_set = same_set|>List.map(fun fn->fn:?>Content.TextContent) + text_fmt (prefix+" ") (obj_def.rows()) typed_set + | _ -> failwith "mismatch" + ) + let subs = match contents with + | [] -> "" + | _ -> contents|> List.reduce(fun a b -> a + "\n" + b) + $"{prefix}{{情节 {obj_def.name()}{subs}\n{prefix}}}" + + + // 格式化Story:Node + let story_def_fmt(node: Content.StoryDefine): string = + let obj_story = (node:> Present.IDomUnit).object():?> AstImport.StoryDef + + let childs = (node:> Present.IContainer).children() + let same_groups_set = same_fold(childs) + let contents = same_groups_set |> List.map(fun same_set -> + if same_set.Length = 0 then + "" + else + match same_set.Head with + | :? Content.FragmentSlice -> + let strx = same_set|> List.map(fun n -> + slice_def_fmt " " (n:?> Content.FragmentSlice) + ) + strx |> List.reduce(fun a b -> a + "\n" + b) + | :? Content.TextContent -> + let typed_set = same_set|>List.map(fun fn->fn:?>Content.TextContent) + text_fmt " " (obj_story.rows()) typed_set + | _ -> failwith "mismatch-storydef" + ) + let subs = match contents with + | [] -> "" + | _ -> contents|> List.reduce(fun a b -> a + "\n" + b) + $"{{故事 {obj_story.name()}{subs}\n}}" + + + // 格式化Article:Node + let article_def_fmt(prefix:string)(node: Content.ArticleDefine): string = + let obj_article = (node:> Present.IDomUnit).object():?> AstImport.ArticleDef + + let childs = (node:> Present.IContainer).children() + let same_groups_set = same_fold(childs) + let contents = same_groups_set |> List.map(fun same_set -> + if same_set.Length = 0 then + "" + else + match same_set.Head with + | :? Content.FragmentRefer -> + let strx = same_set|> List.map(fun n -> + slice_ref_fmt (prefix+" ") (n:?> Content.FragmentRefer) + ) + strx |> List.reduce(fun a b -> a + "\n" + b) + | :? Content.TextContent -> + let typed_set = same_set|>List.map(fun fn->fn:?>Content.TextContent) + text_fmt (prefix+" ") (obj_article.rows()) typed_set + | _ -> failwith "mismatch" + ) + let subs = match contents with + | [] -> "" + | _ -> contents|> List.reduce(fun a b -> a + "\n" + b) + $"{prefix}{{章节 {obj_article.name()}{subs}\n{prefix}}}" + + + // 格式化Volume:Node + let volume_def_fmt(node: Content.VolumeDefine): string = + let obj_volume = (node:> Present.IDomUnit).object():?> AstImport.VolumeDef + + let childs = (node:> Present.IContainer).children() + let same_groups_set = same_fold(childs) + let contents = same_groups_set |> List.map(fun same_set -> + if same_set.Length = 0 then + "" + else + match same_set.Head with + | :? Content.ArticleDefine -> + let strx = same_set|> List.map(fun n -> + article_def_fmt " " (n:?> Content.ArticleDefine) + ) + strx |> List.reduce(fun a b -> a + "\n" + b) + | :? Content.TextContent -> + let typed_set = same_set |> List.map(fun fn->fn:?>Content.TextContent) + text_fmt " " (obj_volume.rows()) typed_set + | _ -> failwith "mismatch-volumedef" + ) + let subs = match contents with + | [] -> "" + | _ -> contents|> List.reduce(fun a b -> a + "\n" + b) + $"{{分卷 {obj_volume.name()}{subs}\n}}" + + + let document_def_fmt(path:string)(nodes: Present.IDomUnit list):string = + let head_str, rest_nodes=match nodes.Head with + | :? Content.RankDefine -> + rank_fmt(nodes.Head.object()), nodes.Tail + | _ -> "", nodes + let sorted_nodes = rest_nodes|> List.sortBy (fun a->a.object().rows()) + let nodes_str = sorted_nodes|> List.map(fun nx -> + match nx with + | :? Content.VolumeDefine as vdef -> + volume_def_fmt vdef + | :? Content.StoryDefine as sdef-> + story_def_fmt sdef + | _ -> failwith "error-doc-child" + ) + head_str + (nodes_str|> List.reduce(fun a b -> a + "\n" + b)) + + + let program_def_fmt(nodes: Present.IDomUnit list):string list = + let ranks = nodes|> List.filter(fun n -> n:?Content.RankDefine) + + let content_with_rank = ranks|> List.map(fun rankn -> + let rank_o = rankn.object() :?> AstImport.RankDef + let file_path = rank_o.filePath() + + let nodes_within = nodes|> List.filter(fun n -> + match n with + | :? Content.StoryDefine -> + n.object().filePath() = file_path + | :? Content.VolumeDefine -> + n.object().filePath() = file_path + | _ -> false) + document_def_fmt file_path (rankn::nodes_within) + ) + + let nodes_hangout = nodes|> List.filter(fun n -> + let rank_paths = ranks|> List.map(fun n -> n.object().filePath()) + match n with + | :? Content.VolumeDefine -> + not(List.contains (n.object().filePath()) rank_paths) + | _ -> false + ) + + let path_hangout = nodes_hangout|> List.map(fun n -> n.object().filePath()) + |> List.distinct + + let content_without_rank = path_hangout |> List.map(fun path -> + let nodes_within = nodes_hangout|> List.filter(fun n -> n.object().filePath() = path) + document_def_fmt path nodes_within + ) + + content_with_rank@content_without_rank diff --git a/AstConv/HtmlStruct.fs b/AstConv/HtmlStruct.fs index 09280af..77f8a07 100644 --- a/AstConv/HtmlStruct.fs +++ b/AstConv/HtmlStruct.fs @@ -269,6 +269,13 @@ open System.IO ) VolumeDefine(defs, childs) + type RankDefine(defs: AstImport.RankDef) = + class + interface Present.IDomUnit with + member this.name(): string = + raise (System.NotImplementedException()) + member this.object(): AstImport.AstObject = defs + end type UnitGenerate(root: AstImport.AstObject) = class @@ -296,6 +303,8 @@ open System.IO result_nodes@[(refs_depth, ArticleDefine(defs, childs))] | :? AstImport.VolumeDef as defs -> result_nodes@[(refs_depth, VolumeDefine(defs, childs))] + | :? AstImport.RankDef as defs -> + result_nodes@[refs_depth, RankDefine(defs)] | :? AstImport.Program -> result_nodes | _ -> failwith "match error" diff --git a/AstConv/Program.fs b/AstConv/Program.fs index eb20f89..a88b7ec 100644 --- a/AstConv/Program.fs +++ b/AstConv/Program.fs @@ -6,8 +6,9 @@ open HtmlStruct.Present open System.IO open System open System.Diagnostics +open FmtStruct.FmtEntry -let html_generate (file_in:FileInfo) (dir_o:DirectoryInfo) = +let html_generate (file_in:FileInfo) (dir_o:DirectoryInfo): unit = let out_dir = Uri(dir_o.FullName) let doc = XmlDocument() @@ -59,9 +60,24 @@ let html_generate (file_in:FileInfo) (dir_o:DirectoryInfo) = stream2.Flush() Process.Start("dot", $"""-Tsvg -o{Path.Combine(dir_o.FullName, "volume_display.svg")} {Path.Combine(dir_o.FullName, "volume_display.dot")}""") |> ignore +let source_format (file_in:FileInfo): unit = + let doc = XmlDocument() + doc.Load(file_in.FullName) + + let prog = Program.GenerateFrom(doc) + let entry = AstVisitEntry(prog) + let visitor = UnitGenerate(prog) + entry.visitWith(visitor) |> ignore + + // 获取内容节点 + let nodes = visitor.contents() + let strings = program_def_fmt nodes + for it in strings do + printfn $"{it}" + + + let args = System.Environment.GetCommandLineArgs()|> Array.toList - - // 参数校验 match args.Length with | 2 when args.Item(1) = "--help" -> @@ -71,7 +87,10 @@ match args.Length with // 格式化 | 4 when args.Item(1) = "--file" && args.Item(3) = "--format" -> - printfn "开发中" + if not(FileInfo(args.Item 2).Exists) then + printfn "Arguments-Error:指定xast文件不存在" + exit 0 + source_format(FileInfo(args.Item 2)) |> ignore // html生成 | 5 when args.Item(1) = "--file" && args.Item(3) = "--html" -> diff --git a/AstConv/Properties/launchSettings.json b/AstConv/Properties/launchSettings.json index fe1ae1c..deb1f45 100644 --- a/AstConv/Properties/launchSettings.json +++ b/AstConv/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "AstConv": { "commandName": "Project", - "commandLineArgs": "-file \"E:/storyline.xast\" -odir \"E:/\"" + "commandLineArgs": "--file \"E:/storyline.xast\" --format" } } } \ No newline at end of file diff --git a/TestProject/Program.fs b/TestProject/Program.fs new file mode 100644 index 0000000..542ddbc --- /dev/null +++ b/TestProject/Program.fs @@ -0,0 +1,20 @@ +// For more information see https://aka.ms/fsharp-console-apps + +type MarkX() = + class + abstract member some:unit-> unit + default this.some() = + printfn "Hello World!" + end + +type SubMark() = + class + inherit MarkX() + + override this.some (): unit = + printfn "Substract" + end + + +let code = SubMark() +code.some() \ No newline at end of file diff --git a/TestProject/TestProject.fsproj b/TestProject/TestProject.fsproj new file mode 100644 index 0000000..5a3c697 --- /dev/null +++ b/TestProject/TestProject.fsproj @@ -0,0 +1,12 @@ + + + + Exe + net8.0 + + + + + + + diff --git a/WsNovelParser.sln b/WsNovelParser.sln index 374288c..c990b1d 100644 --- a/WsNovelParser.sln +++ b/WsNovelParser.sln @@ -40,6 +40,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CoreTest", "CoreTest\CoreTe EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "AstConv", "AstConv\AstConv.fsproj", "{0C77216C-6484-4C94-BE06-D5D9FF18EA81}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestProject", "TestProject\TestProject.fsproj", "{08F9F7F7-CDB4-4644-92A3-99C41A77CD90}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -134,6 +136,18 @@ Global {0C77216C-6484-4C94-BE06-D5D9FF18EA81}.Release|x64.Build.0 = Release|Any CPU {0C77216C-6484-4C94-BE06-D5D9FF18EA81}.Release|x86.ActiveCfg = Release|Any CPU {0C77216C-6484-4C94-BE06-D5D9FF18EA81}.Release|x86.Build.0 = Release|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Debug|x64.ActiveCfg = Debug|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Debug|x64.Build.0 = Debug|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Debug|x86.ActiveCfg = Debug|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Debug|x86.Build.0 = Debug|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Release|Any CPU.Build.0 = Release|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Release|x64.ActiveCfg = Release|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Release|x64.Build.0 = Release|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Release|x86.ActiveCfg = Release|Any CPU + {08F9F7F7-CDB4-4644-92A3-99C41A77CD90}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WsNovelParser/WsNovelParser.vcxproj b/WsNovelParser/WsNovelParser.vcxproj index f4adeb0..ea2c127 100644 --- a/WsNovelParser/WsNovelParser.vcxproj +++ b/WsNovelParser/WsNovelParser.vcxproj @@ -113,12 +113,12 @@ - + - + diff --git a/WsNovelParser/WsNovelParser.vcxproj.filters b/WsNovelParser/WsNovelParser.vcxproj.filters index a728b41..c9dab74 100644 --- a/WsNovelParser/WsNovelParser.vcxproj.filters +++ b/WsNovelParser/WsNovelParser.vcxproj.filters @@ -29,7 +29,7 @@ Source Files - + Source Files @@ -37,7 +37,7 @@ Header Files - + Header Files diff --git a/WsNovelParser/htmlprint.cpp b/WsNovelParser/astprint.cpp similarity index 99% rename from WsNovelParser/htmlprint.cpp rename to WsNovelParser/astprint.cpp index cea06a4..615466f 100644 --- a/WsNovelParser/htmlprint.cpp +++ b/WsNovelParser/astprint.cpp @@ -1,4 +1,4 @@ -#include "htmlprint.h" +#include "astprint.h" #include #include @@ -86,6 +86,8 @@ bool printer::AstGenerate::visit(std::shared_ptr s dom_slice.setAttribute("name", slice_node->name()); dom_slice.setAttribute("address", (qulonglong) slice_node.get()); + + append_tokens(dom_slice, slice_node); }break; case NovelNode::TextSection: { diff --git a/WsNovelParser/htmlprint.h b/WsNovelParser/astprint.h similarity index 100% rename from WsNovelParser/htmlprint.h rename to WsNovelParser/astprint.h diff --git a/WsNovelParser/main.cpp b/WsNovelParser/main.cpp index c7614a3..255f8b2 100644 --- a/WsNovelParser/main.cpp +++ b/WsNovelParser/main.cpp @@ -14,7 +14,7 @@ #include #include "novelparser.h" -#include "htmlprint.h" +#include "astprint.h" using namespace example_novel; using namespace std;