WsParser_VS/AstConv/HtmlStruct.fs

1104 lines
55 KiB
Forth
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

namespace HtmlStruct
open AstAccess
open System.Xml
open System
open System.IO
/// 展现节点 =========================================================================================
module Present =
/// 内容元素:分卷、章节、引用节点、故事线、情节、定义节点、引用节点
type IDomUnit =
interface
abstract member name:unit -> string
abstract member object:unit -> AstImport.AstObject
end
/// 容器节点:分卷、章节、故事线、情节
type IContainer =
interface
abstract member append:IDomUnit list -> IContainer
abstract member children:unit -> IDomUnit list
end
/// 可访问页面
type PageAccess(name: string, dom: IDomUnit option, childs: IDomUnit list) =
class
let mutable root: string = ""
member this.pageURL(): string = $"{root}{name}.html"
member this.setPageRoot(path: Uri) =
if path.AbsolutePath.EndsWith "/" then
root <- path.AbsolutePath
else
root <- path.AbsolutePath + "/"
abstract member makeNew:string -> IDomUnit option -> IDomUnit list -> PageAccess
default this.makeNew n d cs : PageAccess = failwith "需要在子类中实现makeNew方法"
interface IDomUnit with
member this.name():string = dom.Value.name()
member this.object() = dom.Value.object()
interface IContainer with
member this.children(): IDomUnit list = childs
member this.append(new_list: IDomUnit list): IContainer =
this.makeNew name dom (childs@new_list) :> IContainer
end
/// 内容定义元素 =================================================================================
type Forward =
interface
abstract member elementID:unit -> string
abstract member definedURL:unit -> string
abstract member setDefines:PageAccess -> unit
abstract member assembleURL:unit -> string
abstract member setAssemble:PageAccess -> unit
end
/// 回访定义元素 =================================================================================
type Backward =
interface
abstract member defsElement:unit -> Forward
abstract member elementID:unit -> string
abstract member backwardsLink:unit -> string
abstract member referAnchor:unit -> string
end
type AssembleForward(item: AstImport.AstObject) =
class
let mutable defines_page: Option<PageAccess> = None
let mutable assemble_page: Option<PageAccess> = None
interface Forward with
member this.elementID(): string = item.address()
/// 节点定义页面本元素完整URL
member this.definedURL(): string =
match defines_page with
| Some v -> $"{v.pageURL()}#{(this:>Forward).elementID()}"
| None -> failwith "节点元素定义页面配置错误"
member this.setDefines(page: PageAccess) =
defines_page <- Some(page)
/// 节点汇集页面本元素完整URL
member this.assembleURL():string =
match assemble_page with
| Some v -> $"{v.pageURL()}#{(this:>Forward).elementID()}"
| None -> failwith "节点元素汇集页面配置错误"
member this.setAssemble(page: PageAccess) =
assemble_page <- Some(page)
end
type AssembleBackward(bind_item: Forward, refer_anchor: string) =
class
interface Backward with
member this.defsElement() = bind_item
member this.elementID(): string = bind_item.elementID()
member this.backwardsLink(): string = bind_item.definedURL()
member this.referAnchor(): string = refer_anchor
end
/// 访问页面:概括页面、卷宗页面、故事线页面、节点汇总 =================================================
type VolumePage(page_name: string, volume: IDomUnit option, childs: IDomUnit list) =
class
inherit PageAccess(page_name, volume, childs)
new(page_name, volume) = VolumePage(page_name, volume, [])
override this.makeNew (n: string) (d: IDomUnit option) (cs: IDomUnit list): PageAccess =
VolumePage(n, d, cs)
end
type StoryPage(page_name: string, story: IDomUnit option, childs: IDomUnit list) =
class
inherit PageAccess(page_name, story, childs)
new(page_name, story) = StoryPage(page_name, story, [])
override this.makeNew (n: string) (d: IDomUnit option) (cs: IDomUnit list): PageAccess =
StoryPage(n, d, cs)
end
type SlicePage(page_name: string, point: IDomUnit option, refer_list: IDomUnit list) =
class
inherit PageAccess(page_name, point, refer_list)
new(page_name, point) = SlicePage(page_name, point, [])
member this.defines() = point
override this.makeNew (n: string) (d: IDomUnit option) (cs: IDomUnit list): PageAccess =
SlicePage(n, d, cs)
end
/// 内容节点 =========================================================================================
module Content =
type TextContent(word: AstImport.TextItem) =
class
interface Present.IDomUnit with
member this.name(): string = ""
member this.object() = word
end
type FragmentRefer(refs: AstImport.FragmentRef, items: Present.IDomUnit list) =
class
inherit Present.AssembleForward(refs)
let ref_signature = $"@{refs.storyRef()}&{refs.sliceRef()}"
interface Present.IDomUnit with
member this.name(): string = ref_signature
member this.object() = refs
interface Present.IContainer with
member this.children(): Present.IDomUnit list = items
member this.append(childs: Present.IDomUnit list): Present.IContainer =
failwith "append"
end
let refer_assemble (refn: AstImport.FragmentRef) : FragmentRefer =
let texts = refn.children() |> List.map (fun dom -> TextContent(dom) :> Present.IDomUnit)
FragmentRefer(refn, texts)
/// 具有回退功能的元素
type AssembleRefer<'T when 'T :> Present.Forward
and 'T :> Present.IDomUnit
and 'T :> Present.IContainer
>(item: 'T, refer_anchor: string) =
class
inherit Present.AssembleBackward(item, refer_anchor)
interface Present.IDomUnit with
member this.name() = refer_anchor
member this.object() = item.object()
interface Present.IContainer with
member this.children(): Present.IDomUnit list = item.children()
member this.append(childs: Present.IDomUnit list): Present.IContainer =
failwith "append"
end
type FragmentSlice(defs: AstImport.FragmentSlice, items: Present.IDomUnit list) =
class
inherit Present.AssembleForward(defs)
interface Present.IDomUnit with
member this.name(): string = defs.name()
member this.object() = defs
interface Present.IContainer with
member this.children(): Present.IDomUnit list = items
member this.append(childs: Present.IDomUnit list): Present.IContainer =
failwith "append"
end
let slice_assemble (defs: AstImport.FragmentSlice) : FragmentSlice =
let childs = defs.children()
|> List.map<AstImport.AstObject,Present.IDomUnit>(fun data ->
match data with
| :? AstImport.FragmentRef as defs -> refer_assemble(defs)
| :? AstImport.TextItem as text -> TextContent(text)
| _ -> failwith "match error"
)
FragmentSlice(defs, childs)
type StoryDefine(defs: AstImport.StoryDef, childs: Present.IDomUnit list) =
class
interface Present.IDomUnit with
member this.name(): string =
defs.name()
member this.object() = defs
interface Present.IContainer with
member this.children(): Present.IDomUnit list = childs
member this.append(childs: Present.IDomUnit list): Present.IContainer =
failwith "append"
end
let story_assemble (defs: AstImport.StoryDef): StoryDefine =
let childs = defs.children()
|> List.map<AstImport.AstObject, Present.IDomUnit>(fun data ->
match data with
| :? AstImport.FragmentSlice as defs -> slice_assemble(defs)
| :? AstImport.TextItem as text -> TextContent(text)
| _ -> failwith "match error"
)
StoryDefine(defs, childs)
type ArticleDefine(defs: AstImport.ArticleDef, childs: Present.IDomUnit list) =
class
interface Present.IDomUnit with
member this.name(): string =
defs.name()
member this.object() = defs
interface Present.IContainer with
member this.children(): Present.IDomUnit list = childs
member this.append(childs: Present.IDomUnit list): Present.IContainer =
failwith "append"
end
let article_assemble (defs: AstImport.ArticleDef) : ArticleDefine =
let childs = defs.children()
|> List.map<AstImport.AstObject, Present.IDomUnit>(fun data ->
match data with
| :? AstImport.TextItem as text -> TextContent(text)
| :? AstImport.FragmentRef as refs -> refer_assemble(refs)
| _ -> failwith "match error"
)
ArticleDefine(defs, childs)
type VolumeDefine(defs: AstImport.VolumeDef, childs: Present.IDomUnit list) =
class
interface Present.IDomUnit with
member this.name(): string = defs.name()
member this.object() = defs
interface Present.IContainer with
member this.children(): Present.IDomUnit list = childs
member this.append(news: Present.IDomUnit list): Present.IContainer =
VolumeDefine(defs, childs@news)
end
let volume_assemble (defs: AstImport.VolumeDef): VolumeDefine =
let childs = defs.children()
|> List.map<AstImport.AstObject, Present.IDomUnit>(fun data ->
match data with
| :? AstImport.TextItem as text -> TextContent(text)
| :? AstImport.ArticleDef as defs -> article_assemble(defs)
| _ -> failwith "match error"
)
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
// ast 节点序列
let forwards_map = AstImport.branch_map_conv(root, root.members())
// 累积结果
let mutable result_nodes: (int*Present.IDomUnit) list = []
interface AstImport.AstVisitor with
member this.visit(obj: AstImport.AstObject): bool =
let refs_depth = AstImport.depth_seek(forwards_map, obj)
let childs = result_nodes |> List.filter (fun (depth, _) -> depth > refs_depth)
|> List.map(fun (_, object) -> object)
let datas = match obj with
| :? AstImport.TextItem as text ->
result_nodes@[(refs_depth, TextContent(text) :> Present.IDomUnit)]
| :? AstImport.FragmentRef as defs ->
result_nodes@[(refs_depth, FragmentRefer(defs, childs))]
| :? AstImport.FragmentSlice as defs ->
result_nodes@[(refs_depth, FragmentSlice(defs, childs))]
| :? AstImport.StoryDef as defs ->
result_nodes@[(refs_depth, StoryDefine(defs, childs))]
| :? AstImport.ArticleDef as defs ->
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"
result_nodes <- datas
if refs_depth > 0 then
result_nodes <- result_nodes |> List.filter(fun (depth, _) -> depth <= refs_depth)
true
member this.contents() =
result_nodes |> List.map(fun (_, obj) -> obj)
end
/// 内容组装 =========================================================================================
module Assemble =
/// 构建页面名称
let page_name_encode(node: Present.IDomUnit): string =
let name_seqs = $"{node.name()}_{node.object().address()}".ToCharArray() |> Array.map(fun c-> string(uint16(c)))
name_seqs |> Array.reduce(fun a b-> a+b)
/// 构建所有卷宗页面
let rec volume_page_assemble (nodes: Present.IDomUnit list): Present.VolumePage list =
match nodes with
| [] -> []
| _ ->
match nodes.Head with
| :? Content.VolumeDefine as vole ->
let con: Present.IContainer = vole
Present.VolumePage(page_name_encode(vole), Some(vole), con.children())::volume_page_assemble(nodes.Tail)
| _ -> volume_page_assemble(nodes.Tail)
/// 构建所有故事线页面
let rec story_page_assemble (nodes: Present.IDomUnit list): Present.StoryPage list =
match nodes with
| [] -> []
| _ ->
match nodes.Head with
| :? Content.StoryDefine as storye ->
let con: Present.IContainer = storye
Present.StoryPage(page_name_encode(storye), Some(storye), con.children())::story_page_assemble(nodes.Tail)
| _ -> story_page_assemble(nodes.Tail)
/// 提取fragment-slice
let rec private slice_extract(nodes: Present.IDomUnit list):(Present.IDomUnit option * Present.IDomUnit)list =
match nodes with
| [] -> []
| _ ->
let head_data = nodes.Head
match head_data with
| :? Present.StoryPage as story_e ->
let con = story_e :> Present.IContainer
let items = slice_extract(con.children())@slice_extract(nodes.Tail)
items |> List.map(
fun (x, y)->
match x with
| None -> (Some(story_e :> Present.IDomUnit), y)
| _ -> (x, y)
)
| :? Content.FragmentSlice as slice_e ->
(None, slice_e)::slice_extract(nodes.Tail)
| _ -> slice_extract(nodes.Tail)
/// 提取fragment-refer
let rec private refers_extract(nodes: Present.IDomUnit list):(Present.IDomUnit option * Present.IDomUnit option * Present.IDomUnit) list =
match nodes with
| [] -> []
| _ ->
let head_node = nodes.Head
match head_node with
| :? Content.FragmentRefer as refer_e ->
(None, None, refer_e)::refers_extract(nodes.Tail)
| :? Content.TextContent ->
refers_extract(nodes.Tail)
| :? Present.PageAccess ->
let s = head_node :?> Present.IContainer
let items = refers_extract(s.children())@refers_extract(nodes.Tail)
items |> List.map(fun (x, y, z)->
match x with
| None -> (Some(head_node), y, z)
| _ -> (x, y, z)
)
| :? Present.IContainer as con ->
let rst = refers_extract(con.children())@refers_extract(nodes.Tail)
rst |> List.map(fun (x, y, z) ->
match y with
| None -> (x, Some(head_node), z)
| _ -> (x, y, z)
)
| _ -> failwith "refer-extract mismatch"
/// 构建节点汇总页面
/// param nodes volume-pages@story-pages
let rec slice_page_assemble(pages: Present.IDomUnit list) =
let checked_list = pages |> List.filter(fun it -> it :? Present.PageAccess)
if checked_list.Length <> pages.Length then
failwith "传入的参数pages必须是PageAccess的子类集合"
let refers_forward = refers_extract(pages)
// 所有的节点关联定义页面
refers_forward |> List.iter (fun (page, _, deref) ->
match deref with
| :? Present.Forward as forwards_elm ->
let page_def = page.Value :?> Present.PageAccess
forwards_elm.setDefines(page_def)
| _ -> failwith "point_peers_extract结果类型错误"
)
let refers_backward = refers_forward |> List.map(fun (page_defs, mid_defs, point_refs) ->
let refn = point_refs.object() :?> AstImport.FragmentRef
let refer_name = $"{page_defs.Value.name()}&{mid_defs.Value.name()}@<this>"
let refs = point_refs :?> Content.FragmentRefer
$"{refn.storyRef()}&{refn.sliceRef()}", Content.AssembleRefer<Content.FragmentRefer>(refs, refer_name)
)
let slices_forward = slice_extract(pages)
// 构建节点汇集页面
let slices_backward = slices_forward |> List.map (fun (page_defs, slice_defs) ->
let defs = slice_defs :?> Content.FragmentSlice
let name_seqs = $"{page_defs.Value.name()}&{slice_defs.name()}"
let refer_point = Content.AssembleRefer<Content.FragmentSlice>(defs, name_seqs)
let page_assemble = Present.SlicePage(page_name_encode(defs), Some(refer_point))
let insf = defs :> Present.Forward
insf.setAssemble(page_assemble)
insf.setDefines(page_defs.Value :?> Present.PageAccess)
name_seqs,page_assemble
)
slices_backward |> List.map(fun (key, page_one) ->
let page_old = page_one :> Present.IContainer
let page_dom = page_one :> Present.IDomUnit
let nodes = refers_backward |> List.filter(fun (k, _) -> k = key)
nodes |> List.iter (fun (_, data) -> (data:>Present.Backward).defsElement().setAssemble(page_one))
let p = page_old.append(page_dom::(nodes |> List.map(fun (_, x)->x :> Present.IDomUnit)))
p :?> Present.IDomUnit
)
type PageText =
interface
abstract member bindPage:unit ->obj
abstract member getHtmlText:unit -> string
end
type PageMaker(page: Present.PageAccess) =
class
member this.getHtmlDocument(): XmlDocument =
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 title = doc.CreateElement("title")
let page_dom = page :> Present.IDomUnit
title.AppendChild(doc.CreateTextNode(page_dom.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: 20px;
max-width: 90%;
outline: #e0e0e0 solid 1px;
box-shadow: 2px 2px 2px gray;
}
.content p {
word-wrap:break-word;
}
img {
max-width:100%;
}
""")) |> 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(page_dom.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
let page_set = page :> Present.IContainer
this.contentAppend(div_content, page_set.children())
doc
member this.initNavigate(nav: XmlElement):XmlElement =
/// 导航栏生成
let rec nav_items_expand(nav: XmlElement, items: Present.IDomUnit list) =
match items with
| [] -> ()
| _ ->
let doc_o = nav.OwnerDocument
let head = items.Head
match head with
| :? Present.Forward as fx ->
let p = doc_o.CreateElement("p")
let a = doc_o.CreateElement("a")
a.SetAttribute("href", $"#{fx.elementID()}")
a.AppendChild(doc_o.CreateTextNode(head.name())) |> ignore
p.AppendChild(a) |> ignore
nav.AppendChild(p) |> ignore
| :? Present.Backward as bx ->
let p = doc_o.CreateElement("p")
let a = doc_o.CreateElement("a")
a.SetAttribute("href", $"#{bx.elementID()}")
a.AppendChild(doc_o.CreateTextNode(head.name())) |> ignore
p.AppendChild(a) |> ignore
nav.AppendChild(p) |> ignore
| _ -> ()
nav_items_expand(nav, items.Tail)
/// 导航条目提取
let rec nav_items_extract(items: Present.IDomUnit list) : Present.IDomUnit list =
match items with
| [] -> []
| _ ->
let head = items.Head
match head with
| :? Present.Forward ->
let s = head :?> Present.IContainer
head::nav_items_extract(s.children())@nav_items_extract(items.Tail)
| :? Present.Backward as bx->
let s = head :?> Present.IContainer
head::nav_items_extract(s.children())@nav_items_extract(items.Tail)
| :? Present.IContainer as set->
nav_items_extract(set.children())@nav_items_extract(items.Tail)
| _ -> nav_items_extract(items.Tail)
let point_peers = nav_items_extract [page]
match point_peers with
| [] ->
let empty = nav.OwnerDocument.CreateElement("p")
empty.AppendChild(nav.OwnerDocument.CreateTextNode("当前文档无索引")) |> ignore
nav.AppendChild(empty) |> ignore
| _ ->
nav_items_expand(nav, point_peers)
nav
member this.contentAppend(pnode: XmlElement, contents: Present.IDomUnit list) =
let vdoc = pnode.OwnerDocument
match contents with
| [] -> ()
| _ ->
match contents.Head with
| :? Content.TextContent ->
let text_ast = contents.Head.object() :?> AstImport.TextItem
let text_line = contents.Tail |> List.filter(
fun content_item->
match content_item with
| :? Content.TextContent ->
let curr_ast = content_item.object() :?> AstImport.TextItem
curr_ast.row() = text_ast.row()
| _ -> false
)
let one_line = contents.Head::text_line
let p = vdoc.CreateElement("p")
for t in one_line do
let text_content = t.object() :?> AstImport.TextItem
let content = text_content.content()
p.AppendChild(vdoc.CreateTextNode(content)) |> ignore
pnode.AppendChild(p) |> ignore
this.contentAppend(pnode, List.skip one_line.Length contents)
| :? Content.ArticleDefine as article_defs ->
let div_article = vdoc.CreateElement("div")
div_article.SetAttribute("data-type", "article")
div_article.SetAttribute("class", "content")
let article_title = vdoc.CreateElement("h3")
article_title.AppendChild(vdoc.CreateTextNode(contents.Head.name())) |> ignore
div_article.AppendChild(article_title) |> ignore
pnode.AppendChild(div_article) |> ignore
let article_con = article_defs :> Present.IContainer
this.contentAppend(div_article, article_con.children())
this.contentAppend(pnode, contents.Tail)
| :? Present.SlicePage as page_point ->
let page_defs = page_point.defines().Value :?> Content.AssembleRefer<Content.FragmentSlice>
this.contentAppend(pnode, [page_defs])
this.contentAppend(pnode, contents.Tail)
| :? Present.Forward as object_defs ->
let type_name = match object_defs with
| :? Content.FragmentSlice -> "slice"
| :? Content.FragmentRefer -> "refer"
| _ -> failwith "type mismatch"
let div_forwards = vdoc.CreateElement("div")
div_forwards.SetAttribute("data-type", type_name)
div_forwards.SetAttribute("class", "content")
let define_title = vdoc.CreateElement("h3")
define_title.AppendChild(vdoc.CreateTextNode(contents.Head.name())) |> ignore
div_forwards.AppendChild(define_title) |> ignore
let define_a = vdoc.CreateElement("a")
define_a.SetAttribute("id", object_defs.elementID())
define_a.SetAttribute("href", object_defs.assembleURL())
define_a.AppendChild(define_title) |> ignore
div_forwards.AppendChild(define_a) |> ignore
pnode.AppendChild(div_forwards) |> ignore
let point_con = contents.Head :?> Present.IContainer
this.contentAppend(div_forwards, point_con.children())
this.contentAppend(pnode, contents.Tail)
| :? Present.Backward as object_backs ->
let div_backwards = vdoc.CreateElement("div")
div_backwards.SetAttribute("data-type", "assemble")
div_backwards.SetAttribute("class", "content")
let define_title = vdoc.CreateElement("h3")
define_title.AppendChild(vdoc.CreateTextNode(contents.Head.name())) |> ignore
div_backwards.AppendChild(define_title) |> ignore
let define_a = vdoc.CreateElement("a")
define_a.SetAttribute("id", object_backs.elementID())
define_a.SetAttribute("href", object_backs.backwardsLink())
define_a.AppendChild(define_title) |> ignore
div_backwards.AppendChild(define_a) |> ignore
pnode.AppendChild(div_backwards) |> ignore
let point_con = contents.Head :?> Present.IContainer
this.contentAppend(div_backwards, point_con.children())
this.contentAppend(pnode, contents.Tail)
| _ -> ()
interface PageText with
member this.bindPage():obj =
page
member this.getHtmlText(): string =
let doc = this.getHtmlDocument()
let out = new StringWriter()
let writer = new XmlTextWriter(out)
writer.Formatting = Formatting.Indented |> ignore
doc.WriteTo(writer)
writer.Flush()
out.ToString()
end
type IndexPage(name: string, childs: Present.IDomUnit list) =
class
inherit Present.PageAccess("index", None, childs)
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 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: 20px;
max-width: 90%;
outline: #e0e0e0 solid 1px;
box-shadow: 2px 2px 2px gray;
}
.content p {
word-wrap:break-word;
}
img {
max-width:100%;
}
""")) |> 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 story_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.nodedefs_collect(objs: AstImport.AstObject list) =
match objs with
| [] -> []
| _ ->
match objs.Head with
| :? AstImport.StoryDef as story ->
let list_a = this.nodedefs_collect(story.children())
(list_a|> List.map(fun (_, b) -> Some(story), b))@this.nodedefs_collect(objs.Tail)
| :? AstImport.FragmentSlice as slice ->
(None, slice)::this.nodedefs_collect(objs.Tail)
| _ -> this.nodedefs_collect(objs.Tail)
member this.getGraphCode(): string =
let fragment_defs = this.nodedefs_collect(story_lines|> List.map(fun d -> d:>AstImport.AstObject))
// 故事线声明
let story_decl = story_lines|> List.map(fun story ->
$"""node_{story.address()}[label="{story.name()}" shape="cds"]
""")|> List.reduce(fun a b -> a + b)
// 情节节点声明
let points_decl = fragment_defs |> List.map(fun (_, node_def) ->
$"""node_{node_def.address()}[label="{node_def.name()}" shape="rect"]
""")|> List.reduce(fun a b -> a + b)
// 所有的节点声明
let nodes_decl = story_decl + points_decl
// 故事线内节点连线
let rec node_chains(story_nm: string)(node_curr:AstImport.AstObject)(nexts:AstImport.AstObject list): string list =
match nexts with
| [] -> []
| _ ->
let vhead = nexts.Head
match vhead with
| :? AstImport.FragmentSlice as slice ->
$"""node_{node_curr.address()}->node_{vhead.address()}[label="{story_nm}"]
"""::node_chains story_nm slice nexts.Tail
| _ -> node_chains story_nm node_curr nexts.Tail
let arrows_decl = story_lines|> List.map(fun story -> node_chains (story.name()) story (story.children()))
|> List.reduce (fun al bl -> al @ bl)
|> List.reduce (fun a b -> a + b)
// 获取节点字典
let node_map = fragment_defs |> List.map(fun (a, b) ->
$"{a.Value.name()}&{b.name()}", b.address())
// 故事线之间的节点连线
let towards_arrs = fragment_defs|> List.map(fun (_, defn) ->
let node_refs = defn.children() |> List.filter(fun v -> v:? AstImport.FragmentRef)
|> List.map(fun v -> v:?> AstImport.FragmentRef)
node_refs|> List.map(fun nref ->
let node_name = $"""{nref.storyRef()}&{nref.sliceRef()}"""
let _, address_target = node_map|> List.filter(fun (a,_)-> a = node_name)|> List.item 0
$"""node_{defn.address()}->node_{address_target}[style="dotted"]
"""
)
)
|> List.reduce(fun al bl-> al @ bl)
|> List.reduce(fun a b -> a + b)
let all_arrows = arrows_decl + towards_arrs
$"""digraph node_relates{{
rankdir=LR
label="{gname}"
{nodes_decl}
{all_arrows}
}}"""
end
type VolumeGraphMake(gname: string, story_volume_set: Present.PageAccess list) =
class
member private this.nodedefs_collect(objs: AstImport.AstObject list) =
match objs with
| [] -> []
| _ ->
match objs.Head with
| :? AstImport.StoryDef as story ->
let list_a = this.nodedefs_collect(story.children())
(list_a|> List.map(fun (_, b) -> Some(objs.Head), b))@this.nodedefs_collect(objs.Tail)
| :? AstImport.FragmentSlice as slice ->
(None, objs.Head)::this.nodedefs_collect(objs.Tail)
| :? AstImport.VolumeDef as volume ->
let list_a = this.nodedefs_collect(volume.children())
(list_a|> List.map(fun (_, b)-> Some(objs.Head), b))@this.nodedefs_collect(objs.Tail)
| :? AstImport.ArticleDef as article ->
(None, objs.Head)::this.nodedefs_collect(objs.Tail)
| _ -> this.nodedefs_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())
// 生成所有cluster
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 sub_content = point_code_generate(story.children())
let content_desc = if sub_content.Length > 0 then sub_content|> List.reduce(fun a b -> a + b) else ""
let story_desc = $"""subgraph cluster_{story.address()} {{ label="{story.name()}" {content_desc} }}
"""
story_desc::point_code_generate(list.Tail)
| :? AstImport.FragmentSlice as slice ->
let slice_desc = $"""node_{slice.address()}[label="{slice.name()}" shape="rect"]
"""
slice_desc::point_code_generate(list.Tail)
| :? AstImport.VolumeDef as volume ->
let sub_content = point_code_generate(volume.children())
let content_desc = if sub_content.Length > 0 then sub_content|> List.reduce(fun a b -> a + b) else ""
let volume_desc = $""" subgraph cluster_{volume.address()} {{ label="{volume.name()}" {content_desc} }}
"""
volume_desc::point_code_generate(list.Tail)
| :? AstImport.ArticleDef as article ->
let article_desc = $"""node_{article.address()}[label="{article.name()}"]
"""
article_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 + b)
// 提取所有可视节点
let child_nodes = this.nodedefs_collect(storys)@this.nodedefs_collect(volumes)
// 获取节点字典
let node_map = child_nodes|> List.filter(fun (_, b) -> b:? AstImport.FragmentSlice)
|> List.map(fun (a, b) ->
let story_e = a.Value :?> AstImport.StoryDef
let fragm_e = b :?> AstImport.FragmentSlice
$"{story_e.name()}&{fragm_e.name()}", b.address())
let rst = child_nodes |> List.filter(fun (_, b) -> b:? AstImport.ArticleDef) // 筛选文章节点
|> List.map(fun (_, b) -> b:?> AstImport.ArticleDef) // 获取文章节点
|> List.map(fun v -> v, (v.children()|> List.filter(fun v-> v:? AstImport.FragmentRef))) // 获取文章节点和他的引用线
|> List.filter(fun (_, al)-> al.Length > 0)
let refers = rst|> List.map(fun (a, refsa) ->
refsa|> List.map(fun refn ->
let refx = refn :?> AstImport.FragmentRef
let target_name = $"""{refx.storyRef()}&{refx.sliceRef()}"""
let _, target_address = node_map|> List.filter(fun (a, _) -> a = target_name)|> List.item 0
$"""node_{a.address()}--node_{target_address}
"""
)
)
|> List.reduce (fun a b -> a @ b)
|> List.reduce (fun a b -> a + b)
$"""graph{{ label="{gname}" {clusters_desc} {refers} }}"""
end