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 = None
let mutable assemble_page: Option = 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(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(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(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(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 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.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()}@"
let refs = point_refs :?> Content.FragmentRefer
$"{refn.storyRef()}&{refn.sliceRef()}", Content.AssembleRefer(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(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
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.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