diff options
author | bptato <nincsnevem662@gmail.com> | 2025-01-05 15:56:55 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-01-05 16:03:24 +0100 |
commit | 2de3aba952c992361b93d5a9e0059fe8892b38df (patch) | |
tree | 8fb17f0b8411c3bd170f14f72111ef6dcff6433d | |
parent | 3978c59cde65a14883d165b47d4549cb62fd248b (diff) | |
download | chawan-2de3aba952c992361b93d5a9e0059fe8892b38df.tar.gz |
dom: clean up namespace handling, add createElementNS
-rw-r--r-- | src/html/catom.nim | 49 | ||||
-rw-r--r-- | src/html/chadombuilder.nim | 2 | ||||
-rw-r--r-- | src/html/dom.nim | 155 |
3 files changed, 139 insertions, 67 deletions
diff --git a/src/html/catom.nim b/src/html/catom.nim index dfceb965..3f5bd716 100644 --- a/src/html/catom.nim +++ b/src/html/catom.nim @@ -69,6 +69,12 @@ macro makeStaticAtom = satMousewheel = "mousewheel" satMultiple = "multiple" satName = "name" + satNamespaceHTML = "http://www.w3.org/1999/xhtml", + satNamespaceMathML = "http://www.w3.org/1998/Math/MathML", + satNamespaceSVG = "http://www.w3.org/2000/svg", + satNamespaceXLink = "http://www.w3.org/1999/xlink", + satNamespaceXML = "http://www.w3.org/XML/1998/namespace", + satNamespaceXMLNS = "http://www.w3.org/2000/xmlns/", satNomodule = "nomodule" satNovalidate = "novalidate" satOnclick = "onclick" @@ -104,12 +110,16 @@ macro makeStaticAtom = satTouchstart = "touchstart" satType = "type" satUEvent = "Event" + satUempty = "" satUsemap = "usemap" satUsername = "username" satValign = "valign" satValue = "value" satWheel = "wheel" satWidth = "width" + satXlink = "xlink" + satXml = "xml" + satXmlns = "xmlns" let decl = quote do: type StaticAtom* {.inject.} = enum atUnknown = "" @@ -149,17 +159,21 @@ type strMap: array[CAtomFactoryStrMapLength, seq[CAtom]] atomMap: seq[string] lowerMap: seq[CAtom] + namespaceMap: array[Namespace, CAtom] + prefixMap: array[NamespacePrefix, CAtom] #TODO could be a ptr probably CAtomFactory* = ref CAtomFactoryObj +# This maps to JS null. const CAtomNull* = CAtom(0) # Mandatory Atom functions func `==`*(a, b: CAtom): bool {.borrow.} func hash*(atom: CAtom): Hash {.borrow.} -func `$`*(a: CAtom): string {.borrow.} +when defined(debug): + func `$`*(a: CAtom): string {.borrow.} func toAtom(factory: var CAtomFactoryObj; s: string; isInit: static bool = false): CAtom = @@ -195,6 +209,18 @@ const factoryInit = (func(): CAtomFactoryInit = # fill slots of newly added lower mappings while init.obj.lowerMap.len < init.obj.atomMap.len: init.obj.lowerMap.add(CAtom(init.obj.lowerMap.len)) + let olen = init.obj.atomMap.len + for it in Namespace: + if it == NO_NAMESPACE: + init.obj.namespaceMap[it] = CAtomNull + else: + init.obj.namespaceMap[it] = init.obj.toAtom($it) + for it in NamespacePrefix: + if it == NO_PREFIX: + init.obj.prefixMap[it] = CAtomNull + else: + init.obj.prefixMap[it] = init.obj.toAtom($it) + assert init.obj.atomMap.len == olen return init )() @@ -237,7 +263,7 @@ func toStr*(factory: CAtomFactory; atom: CAtom): string = func toStr*(factory: CAtomFactory; sa: StaticAtom): string = return factory.toStr(factory.toAtom(sa)) -func toTagType*(factory: CAtomFactory; atom: CAtom): TagType = +func toTagType*(atom: CAtom): TagType = let i = int(atom) if i <= int(TagType.high): return TagType(i) @@ -252,6 +278,23 @@ func toStaticAtom*(factory: CAtomFactory; atom: CAtom): StaticAtom = func toStaticAtom*(factory: CAtomFactory; s: string): StaticAtom = return factory.toStaticAtom(factory.toAtom(s)) +func toNamespace*(factory: CAtomFactory; atom: CAtom): Namespace = + case factory.toStaticAtom(atom) + of satUempty: return NO_NAMESPACE + of satNamespaceHTML: return Namespace.HTML + of satNamespaceMathML: return Namespace.MATHML + of satNamespaceSVG: return Namespace.SVG + of satNamespaceXLink: return Namespace.XLINK + of satNamespaceXML: return Namespace.XML + of satNamespaceXMLNS: return Namespace.XMLNS + else: return NAMESPACE_UNKNOWN + +func toAtom*(factory: CAtomFactory; namespace: Namespace): CAtom = + return factory.namespaceMap[namespace] + +func toAtom*(factory: CAtomFactory; prefix: NamespacePrefix): CAtom = + return factory.prefixMap[prefix] + # Forward declaration hack var getFactoryImpl*: proc(ctx: JSContext): CAtomFactory {.nimcall, noSideEffect, raises: [].} @@ -293,6 +336,8 @@ proc fromJS*(ctx: JSContext; val: JSAtom; res: var StaticAtom): Opt[void] = return ok() proc toJS*(ctx: JSContext; atom: CAtom): JSValue = + if atom == CAtomNull: + return JS_NULL return ctx.toJS(ctx.getFactoryImpl().toStr(atom)) proc toJS*(ctx: JSContext; atom: StaticAtom): JSValue = diff --git a/src/html/chadombuilder.nim b/src/html/chadombuilder.nim index ac55999c..32e880bf 100644 --- a/src/html/chadombuilder.nim +++ b/src/html/chadombuilder.nim @@ -53,7 +53,7 @@ proc getDocumentImpl(builder: ChaDOMBuilder): Node = return builder.document proc atomToTagTypeImpl(builder: ChaDOMBuilder; atom: CAtom): TagType = - return builder.factory.toTagType(atom) + return atom.toTagType() proc tagTypeToAtomImpl(builder: ChaDOMBuilder; tagType: TagType): CAtom = return builder.factory.toAtom(tagType) diff --git a/src/html/dom.nim b/src/html/dom.nim index 5aa29474..b5760b4f 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -248,8 +248,8 @@ type value*: string Element* = ref object of Node - namespace*: Namespace - namespacePrefix*: NamespacePrefix + namespaceURI {.jsget.}: CAtom + prefix {.jsget.}: CAtom internalHover: bool invalid*: bool # The owner StyledNode is marked as invalid when one of these no longer @@ -1093,9 +1093,6 @@ proc toAtom*(document: Document; at: StaticAtom): CAtom = proc toStr(document: Document; atom: CAtom): string = return document.factory.toStr(atom) -proc toTagType*(document: Document; atom: CAtom): TagType = - return document.factory.toTagType(atom) - proc toStaticAtom(document: Document; atom: CAtom): StaticAtom = return document.factory.toStaticAtom(atom) @@ -1103,17 +1100,16 @@ proc toAtom*(document: Document; tagType: TagType): CAtom = return document.factory.toAtom(tagType) proc toAtom(document: Document; namespace: Namespace): CAtom = - #TODO optimize - assert namespace != NO_NAMESPACE - return document.toAtom($namespace) + return document.factory.toAtom(namespace) proc toAtom(document: Document; prefix: NamespacePrefix): CAtom = - #TODO optimize - assert prefix != NO_PREFIX - return document.toAtom($prefix) + return document.factory.toAtom(prefix) + +func namespace*(element: Element): Namespace = + return element.document.factory.toNamespace(element.namespaceURI) func tagTypeNoNS(element: Element): TagType = - return element.document.toTagType(element.localName) + return element.localName.toTagType() func tagType*(element: Element; namespace = Namespace.HTML): TagType = if element.namespace != namespace: @@ -1485,6 +1481,12 @@ proc setCookie(ctx: JSContext; document: Document; cookie: string) document.internalCookie = cookie # DOMTokenList +proc newDOMTokenList(element: Element; name: StaticAtom): DOMTokenList = + return DOMTokenList( + element: element, + localName: element.document.toAtom(name) + ) + iterator items*(tokenList: DOMTokenList): CAtom {.inline.} = for tok in tokenList.toks: yield tok @@ -1610,19 +1612,18 @@ proc getter(ctx: JSContext; this: DOMTokenList; atom: JSAtom): JSValue return ctx.item(this, u).uninitIfNull() return JS_UNINITIALIZED -# DOMStringMap -func validateAttributeName(name: string): Err[DOMException] = - if name.matchNameProduction(): - return ok() - return errDOMException("Invalid character in attribute name", - "InvalidCharacterError") +proc validateName(name: string): DOMResult[void] = + if not name.matchNameProduction(): + return errDOMException("Invalid character in name", "InvalidCharacterError") + ok() -func validateAttributeQName(name: string): Err[DOMException] = - if name.matchQNameProduction(): - return ok() - return errDOMException("Invalid character in attribute name", - "InvalidCharacterError") +proc validateQName(qname: string): DOMResult[void] = + if not qname.matchQNameProduction(): + return errDOMException("Invalid character in qualified name", + "InvalidCharacterError") + ok() +# DOMStringMap proc delete(map: var DOMStringMap; name: string): bool {.jsfunc.} = let name = map.target.document.toAtom("data-" & name.camelToKebabCase()) let i = map.target.findAttr(name) @@ -1648,7 +1649,7 @@ proc setter(map: var DOMStringMap; name, value: string): Err[DOMException] return errDOMException("Lower case after hyphen is not allowed in dataset", "InvalidCharacterError") let name = "data-" & name.camelToKebabCase() - ?name.validateAttributeName() + ?name.validateName() let aname = map.target.document.toAtom(name) map.target.attr(aname, value) return ok() @@ -3405,16 +3406,16 @@ func newComment(ctx: JSContext; data: string = ""): Comment {.jsctor.} = return window.document.newComment(data) #TODO custom elements -proc newElement*(document: Document; localName: CAtom; - namespace = Namespace.HTML; prefix = NO_PREFIX): Element = - let tagType = document.toTagType(localName) +proc newElement*(document: Document; localName, namespaceURI, prefix: CAtom): + Element = + let tagType = localName.toTagType() + let sns = document.toStaticAtom(namespaceURI) let element: Element = case tagType of TAG_INPUT: HTMLInputElement() of TAG_A: - let anchor = HTMLAnchorElement() - let localName = document.toAtom(satRel) - anchor.relList = DOMTokenList(element: anchor, localName: localName) + let anchor = HTMLAnchorElement(internalDocument: document) + anchor.relList = anchor.newDOMTokenList(satRel) anchor of TAG_SELECT: HTMLSelectElement() @@ -3439,14 +3440,12 @@ proc newElement*(document: Document; localName: CAtom; of TAG_STYLE: HTMLStyleElement() of TAG_LINK: - let link = HTMLLinkElement() - let localName = document.toAtom(satRel) - link.relList = DOMTokenList(element: link, localName: localName) + let link = HTMLLinkElement(internalDocument: document) + link.relList = link.newDOMTokenList(satRel) link of TAG_FORM: - let form = HTMLFormElement() - let localName = document.toAtom(satRel) - form.relList = DOMTokenList(element: form, localName: localName) + let form = HTMLFormElement(internalDocument: document) + form.relList = form.newDOMTokenList(satRel) form of TAG_TEMPLATE: let templ = HTMLTemplateElement(content: newDocumentFragment(document)) @@ -3482,9 +3481,8 @@ proc newElement*(document: Document; localName: CAtom; of TAG_AUDIO: HTMLAudioElement() of TAG_AREA: - let area = HTMLAreaElement() - let localName = document.toAtom(satRel) - area.relList = DOMTokenList(element: area, localName: localName) + let area = HTMLAreaElement(internalDocument: document) + area.relList = area.newDOMTokenList(satRel) area of TAG_TABLE: HTMLTableElement() @@ -3494,7 +3492,7 @@ proc newElement*(document: Document; localName: CAtom; HTMLTableRowElement() of TAG_TBODY, TAG_THEAD, TAG_TFOOT: HTMLTableSectionElement() - elif namespace == Namespace.SVG: + elif sns == satNamespaceSVG: if tagType == TAG_SVG: SVGSVGElement() else: @@ -3502,18 +3500,22 @@ proc newElement*(document: Document; localName: CAtom; else: HTMLElement() element.localName = localName - element.namespace = namespace - element.namespacePrefix = prefix + element.namespaceURI = namespaceURI + element.prefix = prefix element.internalDocument = document - let localName = document.toAtom(satClassList) - element.classList = DOMTokenList(element: element, localName: localName) + element.classList = element.newDOMTokenList(satClassList) element.index = -1 element.elIndex = -1 - if namespace == Namespace.HTML: + if sns == satNamespaceHTML: let element = HTMLElement(element) element.dataset = DOMStringMap(target: element) return element +proc newElement*(document: Document; localName: CAtom; + namespace = Namespace.HTML; prefix = NO_PREFIX): Element = + return document.newElement(localName, document.toAtom(namespace), + document.toAtom(prefix)) + proc newHTMLElement*(document: Document; tagType: TagType): HTMLElement = let localName = document.toAtom(tagType) return HTMLElement(document.newElement(localName, Namespace.HTML, NO_PREFIX)) @@ -4215,7 +4217,7 @@ proc attrulgz(element: Element; name: StaticAtom; value: uint32) = proc setAttribute(element: Element; qualifiedName, value: string): Err[DOMException] {.jsfunc.} = - ?validateAttributeName(qualifiedName) + ?qualifiedName.validateName() let qualifiedName = if element.namespace == Namespace.HTML and not element.document.isxml: element.document.toAtomLower(qualifiedName) @@ -4226,7 +4228,7 @@ proc setAttribute(element: Element; qualifiedName, value: string): proc setAttributeNS(element: Element; namespace, qualifiedName, value: string): Err[DOMException] {.jsfunc.} = - ?validateAttributeQName(qualifiedName) + ?qualifiedName.validateQName() let ps = qualifiedName.until(':') let prefix = if ps.len < qualifiedName.len: ps else: "" let localName = element.document.toAtom(qualifiedName.substr(prefix.len)) @@ -4265,7 +4267,7 @@ proc removeAttributeNS(element: Element; namespace, localName: string) proc toggleAttribute(element: Element; qualifiedName: string; force = none(bool)): DOMResult[bool] {.jsfunc.} = - ?validateAttributeName(qualifiedName) + ?qualifiedName.validateName() let qualifiedName = element.normalizeAttrQName(qualifiedName) if not element.attrb(qualifiedName): if force.get(true): @@ -5113,11 +5115,9 @@ proc prepare*(element: HTMLScriptElement) = element.execute() #TODO options/custom elements -proc createElement(document: Document; localName: string): - DOMResult[Element] {.jsfunc.} = - if not localName.matchNameProduction(): - return errDOMException("Invalid character in element name", - "InvalidCharacterError") +proc createElement(document: Document; localName: string): DOMResult[Element] + {.jsfunc.} = + ?localName.validateName() let localName = if not document.isxml: document.toAtomLower(localName) else: @@ -5129,16 +5129,45 @@ proc createElement(document: Document; localName: string): NO_NAMESPACE return ok(document.newElement(localName, namespace)) -#TODO createElementNS +proc validateAndExtract(ctx: JSContext; document: Document; namespace: JSValue; + qname: string; namespaceOut, prefixOut, localNameOut: var CAtom): + DOMResult[void] = + ?qname.validateQName() + if JS_IsNull(namespace): + namespaceOut = CAtomNull + else: + ?ctx.fromJS(namespace, namespaceOut) + var prefix = "" + var localName = qname.until(':') + if localName.len < qname.len: + prefix = move(localName) + localName = qname.substr(prefix.len + 1) + if namespaceOut == CAtomNull and prefix != "": + return errDOMException("Got namespace prefix, but no namespace", + "NamespaceError") + let sns = document.toStaticAtom(namespaceOut) + if prefix == "xml" and sns != satNamespaceXML: + return errDOMException("Expected XML namespace", "NamespaceError") + if (qname == "xmlns" or prefix == "xmlns") != (sns == satNamespaceXMLNS): + return errDOMException("Expected XMLNS namespace", "NamespaceError") + prefixOut = if prefix == "": CAtomNull else: document.toAtom(prefix) + localNameOut = document.toAtom(localName) + ok() + +proc createElementNS(ctx: JSContext; document: Document; namespace0: JSValue; + qname: string): DOMResult[Element] {.jsfunc.} = + var namespace, prefix, localName: CAtom + ?ctx.validateAndExtract(document, namespace0, qname, namespace, prefix, + localName) + #TODO custom elements (is) + return ok(document.newElement(localName, namespace, prefix)) proc createDocumentFragment(document: Document): DocumentFragment {.jsfunc.} = return newDocumentFragment(document) proc createDocumentType(implementation: var DOMImplementation; qualifiedName, publicId, systemId: string): DOMResult[DocumentType] {.jsfunc.} = - if not qualifiedName.matchQNameProduction(): - return errDOMException("Invalid character in document type name", - "InvalidCharacterError") + ?qualifiedName.validateQName() let document = implementation.document return ok(document.newDocumentType(qualifiedName, publicId, systemId)) @@ -5196,11 +5225,11 @@ proc clone(node: Node; document = none(Document), deep = false): Node = let copy = if node of Element: #TODO is value let element = Element(node) - let x = document.newElement(element.localName, element.namespace, - element.namespacePrefix) + let x = document.newElement(element.localName, element.namespaceURI, + element.prefix) x.id = element.id x.name = element.name - x.classList = DOMTokenList(element: x, localName: element.localName) + x.classList = x.newDOMTokenList(satClassList) x.attrs = element.attrs #TODO namespaced attrs? # Cloning steps @@ -5297,10 +5326,8 @@ func isEqualNode(node, other: Node): bool {.jsfunc.} = return false let node = Element(node) let other = Element(other) - if node.namespace != other.namespace or - node.namespacePrefix != other.namespacePrefix or - node.localName != other.localName or - node.attrs.len != other.attrs.len: + if node.namespace != other.namespace or node.prefix != other.prefix or + node.localName != other.localName or node.attrs.len != other.attrs.len: return false for i, attr in node.attrs.mypairs: if not attr.equals(other.attrs[i]): |