diff --git a/go.mod b/go.mod index 4a0280b..407afcc 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module forge.capytal.company/loreddev/x +module code.capytal.cc/loreddev/x go 1.23.3 diff --git a/tinyssert/tinyssert.go b/tinyssert/tinyssert.go index c39da85..4b9b424 100644 --- a/tinyssert/tinyssert.go +++ b/tinyssert/tinyssert.go @@ -11,7 +11,7 @@ // under the licenses is distributed on as "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS // OF ANY KIND, either express or implied. // -// An original copy of this file can be found at http://forge.capytal.company/loreddev/x/tinyssert/tinyssert.go. +// An original copy of this file can be found at http://code.capytal.cc/loreddev/x/tinyssert/tinyssert.go. // Package tinyssert is a minimal set of assertions functions for testing and simulation // testing, all in one file. @@ -23,7 +23,7 @@ // // import ( // "log" -// "forge.capytal.company/loreddev/x/tinyssert" +// "code.capytal.cc/loreddev/x/tinyssert" // ) // // func main() { @@ -39,7 +39,7 @@ // // import ( // "log/slog" -// "forge.capytal.company/loreddev/x/tinyssert" +// "code.capytal.cc/loreddev/x/tinyssert" // ) // // var logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{})) @@ -62,7 +62,7 @@ // "flag" // "log/slog" // -// "forge.capytal.company/loreddev/x/tinyssert" +// "code.capytal.cc/loreddev/x/tinyssert" // ) // // var debug = flag.Bool("debug", false, "Run the application in debug mode") diff --git a/xhtml/ast/attributes/global.go b/xhtml/ast/attributes/global.go new file mode 100644 index 0000000..cc7718b --- /dev/null +++ b/xhtml/ast/attributes/global.go @@ -0,0 +1,47 @@ +package attributes + +import "slices" + +type Global struct { + // TODO: Global attributes at + // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes + // https://html.spec.whatwg.org/multipage/dom.html#global-attributes + + class []string + lang string + id string +} + +func (g Global) Class() []string { + return g.class +} + +func (g *Global) SetClass(class []string) { + g.class = class +} + +func (g *Global) AddClass(class string) { + g.class = append(g.class, class) +} + +func (g *Global) DelClass(class string) { + g.class = slices.DeleteFunc(g.class, func(c string) bool { + return c == class + }) +} + +func (g *Global) Lang() string { + return g.lang +} + +func (g *Global) SetLang(lang string) { + g.lang = lang +} + +func (g Global) ID() string { + return g.id +} + +func (g *Global) SetID(id string) { + g.id = id +} diff --git a/xhtml/ast/attributes/shared.go b/xhtml/ast/attributes/shared.go new file mode 100644 index 0000000..8b76bea --- /dev/null +++ b/xhtml/ast/attributes/shared.go @@ -0,0 +1,34 @@ +package attributes + +import "net/url" + +type HRef struct { + href *url.URL +} + +func (a HRef) HRef() *url.URL { + return a.href +} + +func (a *HRef) SetHref(uri *url.URL) { + a.href = uri +} + +type Target struct { + target string +} + +func (a Target) Target() string { + return a.target +} + +func (a *Target) SetTarget(t string) { + a.target = t +} + +var ( + TargetSelf = "_self" + TargetBlank = "_blank" + TargetParent = "_parent" + TargetTop = "_top" +) diff --git a/xhtml/ast/html.go b/xhtml/ast/html.go new file mode 100644 index 0000000..207b116 --- /dev/null +++ b/xhtml/ast/html.go @@ -0,0 +1,56 @@ +package ast + +import ( + "encoding/xml" + + "code.capytal.cc/loreddev/x/xhtml/ast/attributes" +) + +type HTML struct { + version string + xmlns string + + attributes.Global + BaseNode +} + +func (n HTML) Kind() NodeKind { + return KindHTML +} + +var KindHTML = NewNodeKind("html", &HTML{}) + +func (n HTML) Version() string { + return n.version +} + +func (n *HTML) SetVersion(v string) { + n.version = v +} + +func (n HTML) XMLNS() string { + return n.xmlns +} + +func (n *HTML) SetXMLNS(ns string) { + n.xmlns = ns +} + +type Custom struct { + tagName xml.Name + attributes []xml.Attr + + attributes.Global + BaseNode +} + +func (n Custom) Kind() NodeKind { + return KindUnknown +} + +var KindUnknown = NewNodeKind("custom", &Custom{}) + +type Template struct { + attributes.Global + BaseNode +} diff --git a/xhtml/ast/metadata.go b/xhtml/ast/metadata.go new file mode 100644 index 0000000..da07059 --- /dev/null +++ b/xhtml/ast/metadata.go @@ -0,0 +1,51 @@ +package ast + +import ( + "net/url" + "slices" + + "code.capytal.cc/loreddev/x/xhtml/ast/attributes" +) + +type Base struct { + BaseNode + attributes.HRef + attributes.Target + attributes.Global +} + +func (n Base) Kind() NodeKind { + return KindBase +} + +var KindBase = NewNodeKind("base", &Base{}) + +type Head struct { + BaseNode + profile []*url.URL + attributes.Global +} + +func (n Head) Kind() NodeKind { + return KindHead +} + +var KindHead = NewNodeKind("head", &Head{}) + +func (n Head) Profile() []*url.URL { + return n.profile +} + +func (n *Head) SetProfile(uri []*url.URL) { + n.profile = uri +} + +func (n *Head) AddProfile(uri *url.URL) { + n.profile = append(n.profile, uri) +} + +func (n *Head) DelProfile(uri *url.URL) { + n.profile = slices.DeleteFunc(n.profile, func(u *url.URL) bool { + return u.String() == uri.String() + }) +} diff --git a/xhtml/ast/node.go b/xhtml/ast/node.go new file mode 100644 index 0000000..5613137 --- /dev/null +++ b/xhtml/ast/node.go @@ -0,0 +1,199 @@ +package ast + +import "fmt" + +type Node interface { + Kind() NodeKind + + NextSibling() Node + SetNextSibling(Node) + + PreviousSibling() Node + SetPreviousSibling(Node) + + Parent() Node + SetParent(Node) + + HasChildren() bool + ChildCount() uint + + FirstChild() Node + LastChild() Node + + AppendChild(self, v Node) + RemoveChild(self, v Node) + + RemoveChildren(self Node) + ReplaceChild(self, v1, insertee Node) + + InsertBefore(self, v1, insertee Node) + InsertAfter(self, v1, insertee Node) +} + +type BaseNode struct { + next Node + prev Node + parent Node + fisrtChild Node + lastChild Node + childCount uint +} + +func (e *BaseNode) NextSibling() Node { + return e.next +} + +func (e *BaseNode) SetNextSibling(v Node) { + e.next = v +} + +func (e *BaseNode) PreviousSibling() Node { + return e.prev +} + +func (e *BaseNode) SetPreviousSibling(v Node) { + e.prev = v +} + +func (e *BaseNode) Parent() Node { + return e.parent +} + +func (e *BaseNode) SetParent(v Node) { + e.parent = v +} + +func (e *BaseNode) HasChildren() bool { + return e.fisrtChild != nil +} + +func (e *BaseNode) ChildCount() uint { + return e.childCount +} + +func (e *BaseNode) FirstChild() Node { + return e.fisrtChild +} + +func (e *BaseNode) LastChild() Node { + return e.lastChild +} + +func (e *BaseNode) AppendChild(self, v Node) { + ensureIsolated(v) + + if e.fisrtChild == nil { + e.fisrtChild = v + v.SetNextSibling(nil) + v.SetPreviousSibling(nil) + } else { + l := e.lastChild + l.SetNextSibling(v) + v.SetPreviousSibling(l) + } + + v.SetParent(self) + e.lastChild = v + e.childCount++ +} + +func (e *BaseNode) RemoveChild(self, v Node) { + if v.Parent() != self { + return + } + + if e.childCount <= 0 { + e.childCount-- + } + + prev := v.PreviousSibling() + next := v.NextSibling() + + if prev != nil { + prev.SetNextSibling(next) + } else { + e.fisrtChild = next + } + + if next != nil { + next.SetNextSibling(prev) + } else { + e.lastChild = prev + } + + v.SetParent(nil) + v.SetNextSibling(nil) + v.SetPreviousSibling(nil) +} + +func (e *BaseNode) RemoveChildren(_ Node) { + for c := e.fisrtChild; c != nil; { + c.SetParent(nil) + c.SetPreviousSibling(nil) + next := c.NextSibling() + c.SetNextSibling(nil) + c = next + } + e.fisrtChild = nil + e.lastChild = nil + e.childCount = 0 +} + +func (e *BaseNode) ReplaceChild(self, v1, insertee Node) { + e.InsertBefore(self, v1, insertee) + e.RemoveChild(self, v1) +} + +func (e *BaseNode) InsertAfter(self, v1, insertee Node) { + e.InsertBefore(self, v1.NextSibling(), insertee) +} + +func (e *BaseNode) InsertBefore(self, v1, insertee Node) { + e.childCount++ + if v1 == nil { + e.AppendChild(self, insertee) + return + } + + ensureIsolated(insertee) + + if v1.Parent() == self { + c := v1 + prev := c.PreviousSibling() + if prev != nil { + prev.SetNextSibling(insertee) + insertee.SetPreviousSibling(prev) + } else { + e.fisrtChild = insertee + insertee.SetPreviousSibling(nil) + } + insertee.SetNextSibling(c) + c.SetPreviousSibling(insertee) + insertee.SetParent(self) + } +} + +func ensureIsolated(e Node) { + if p := e.Parent(); p != nil { + p.RemoveChild(p, e) + } +} + +type NodeKind string + +var _ fmt.Stringer = NodeKind("") + +func NewNodeKind(kind string, e Node) NodeKind { + k := NodeKind(kind) + if _, ok := elementKindList[k]; ok { + panic(fmt.Sprintf("Node kind %q is already registered", k)) + } + elementKindList[k] = e + return k +} + +func (k NodeKind) String() string { + return string(k) +} + +var elementKindList = make(map[NodeKind]Node) diff --git a/xhtml/xhtml.go b/xhtml/xhtml.go new file mode 100644 index 0000000..90c8189 --- /dev/null +++ b/xhtml/xhtml.go @@ -0,0 +1 @@ +package xhtml diff --git a/xhtml/xhtml_test.go b/xhtml/xhtml_test.go new file mode 100644 index 0000000..fe78460 --- /dev/null +++ b/xhtml/xhtml_test.go @@ -0,0 +1,24 @@ +package xhtml_test + +import ( + "encoding/xml" + "log" + "testing" +) + +func TestMain(t *testing.T) { + type Bold struct { + XMLName xml.Name `xml:"b"` + Content string `xml:",chardata"` + } + type test struct { + XMLName xml.Name `xml:"p"` + Content string `xml:",chardata"` + Bold []Bold `xml:"b"` + } + + var v test + xml.Unmarshal([]byte(`
Hello, world
`), &v) + + log.Printf("%#v", v) +}