diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/html/dom.nim | 341 |
1 files changed, 171 insertions, 170 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim index 985a8b8c..31cf4ccf 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -502,6 +502,7 @@ jsDestructor(CSSStyleDeclaration) # Forward declarations func attr*(element: Element; s: StaticAtom): string func attrb*(element: Element; s: CAtom): bool +func serializeFragment(res: var string; node: Node) func value*(option: HTMLOptionElement): string proc append*(parent, node: Node) proc attr*(element: Element; name: CAtom; value: string) @@ -955,7 +956,7 @@ func attrType0(s: static string): StaticAtom {.compileTime.} = return strictParseEnum[StaticAtom](s).get template toset(ts: openArray[TagType]): set[TagType] = - var tags: system.set[TagType] + var tags: system.set[TagType] = {} for tag in ts: tags.incl(tag) tags @@ -1134,26 +1135,27 @@ func findAttrNS(element: Element; namespace, qualifiedName: CAtom): int = return i return -1 -func escapeText(s: string; attribute_mode = false): string = - var nbsp_mode = false - var nbsp_prev: char +func escapeText(s: string; attributeMode = false): string = + result = newStringOfCap(s.len) + var nbspMode = false + var nbspPrev = '\0' for c in s: - if nbsp_mode: - if c == char(0xA0): + if nbspMode: + if c == '\xA0': result &= " " else: - result &= nbsp_prev & c - nbsp_mode = false + result &= nbspPrev & c + nbspMode = false elif c == '&': result &= "&" - elif c == char(0xC2): - nbsp_mode = true - nbsp_prev = c - elif attribute_mode and c == '"': + elif c == '\xC2': + nbspMode = true + nbspPrev = c + elif attributeMode and c == '"': result &= """ - elif not attribute_mode and c == '<': + elif not attributeMode and c == '<': result &= "<" - elif not attribute_mode and c == '>': + elif not attributeMode and c == '>': result &= ">" else: result &= c @@ -2124,23 +2126,6 @@ func isSubmitButton*(element: Element): bool = return element.inputType in {itSubmit, itImage} return false -func canSubmitImplicitly*(form: HTMLFormElement): bool = - const BlocksImplicitSubmission = { - itText, itSearch, itURL, itTel, itEmail, itPassword, itDate, itMonth, - itWeek, itTime, itDatetimeLocal, itNumber - } - var found = false - for control in form.controls: - if control of HTMLInputElement: - let input = HTMLInputElement(control) - if input.inputType in BlocksImplicitSubmission: - if found: - return false - found = true - elif control.isSubmitButton(): - return false - return true - # https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps proc write(ctx: JSContext; document: Document; args: varargs[JSValue]): Err[DOMException] {.jsfunc.} = @@ -2178,12 +2163,6 @@ func head*(document: Document): HTMLElement {.jsfget.} = func body*(document: Document): HTMLElement {.jsfget.} = return document.findFirst(TAG_BODY) -func select*(option: HTMLOptionElement): HTMLSelectElement = - for anc in option.ancestors: - if anc of HTMLSelectElement: - return HTMLSelectElement(anc) - return nil - func countChildren(node: Node; nodeType: type): int = result = 0 for child in node.childList: @@ -2539,8 +2518,6 @@ func serializesAsVoid(element: Element): bool = const Extra = {TAG_BASEFONT, TAG_BGSOUND, TAG_FRAME, TAG_KEYGEN, TAG_PARAM} return element.tagType in VoidElements + Extra -func serializeFragment(res: var string; node: Node) - func serializeFragmentInner(res: var string; child: Node; parentType: TagType) = if child of Element: let element = Element(child) @@ -2597,7 +2574,7 @@ func serializeFragment*(node: Node): string = result = "" result.serializeFragment(node) -# Element attribute reflection (getters) +# Element func innerHTML(element: Element): string {.jsfget.} = #TODO xml return element.serializeFragment() @@ -2607,6 +2584,7 @@ func outerHTML(element: Element): string {.jsfget.} = result = "" result.serializeFragmentInner(element, TAG_UNKNOWN) +# HTMLElement func crossOrigin0(element: HTMLElement): CORSAttribute = if not element.attrb(satCrossorigin): return caNoCors @@ -2644,59 +2622,6 @@ proc sheets*(document: Document): seq[CSSStylesheet] = document.cachedSheetsInvalid = false return document.cachedSheets -func checked*(input: HTMLInputElement): bool {.inline.} = - return input.internalChecked - -proc setChecked*(input: HTMLInputElement; b: bool) {.jsfset: "checked".} = - if input.inputType == itRadio: - for radio in input.radiogroup: - radio.invalidDeps.incl(dtChecked) - radio.internalChecked = false - radio.setInvalid() - input.invalidDeps.incl(dtChecked) - input.internalChecked = b - input.setInvalid() - -func inputString*(input: HTMLInputElement): string = - case input.inputType - of itCheckbox, itRadio: - if input.checked: - "*" - else: - " " - of itSearch, itText, itEmail, itURL, itTel: - input.value.padToWidth(int(input.attrulgz(satSize).get(20))) - of itPassword: - '*'.repeat(input.value.len).padToWidth(int(input.attrulgz(satSize).get(20))) - of itReset: - if input.attrb(satValue): - input.value - else: - "RESET" - of itSubmit, itButton: - if input.attrb(satValue): - input.value - else: - "SUBMIT" - of itFile: - let s = if input.file != nil: input.file.name else: "" - s.padToWidth(int(input.attrulgz(satSize).get(20))) - else: input.value - -func textAreaString*(textarea: HTMLTextAreaElement): string = - result = "" - let split = textarea.value.split('\n') - let rows = int(textarea.attrul(satRows).get(1)) - for i in 0 ..< rows: - let cols = int(textarea.attrul(satCols).get(20)) - if cols > 2: - if i < split.len: - result &= '[' & split[i].padToWidth(cols - 2) & "]\n" - else: - result &= '[' & ' '.repeat(cols - 2) & "]\n" - else: - result &= "[]\n" - func isButton*(element: Element): bool = if element of HTMLButtonElement: return true @@ -2904,14 +2829,6 @@ proc hyperlinkGetProp(ctx: JSContext; element: HTMLElement; a: JSAtom; return JS_TRUE # dummy value return JS_UNINITIALIZED -# <base> -proc href(base: HTMLBaseElement): string {.jsfget.} = - #TODO with fallback base url - let url = parseURL(base.attr(satHref)) - if url.isSome: - return $url.get - return "" - # <a> proc getter(ctx: JSContext; this: HTMLAnchorElement; a: JSAtom; desc: ptr JSPropertyDescriptor): JSValue {.jsgetownprop.} = @@ -2940,32 +2857,39 @@ proc toString(area: HTMLAreaElement): string {.jsfunc.} = proc setRelList(area: HTMLAreaElement; s: string) {.jsfset: "relList".} = area.attr(satRel, s) -# <label> -func control*(label: HTMLLabelElement): FormAssociatedElement {.jsfget.} = - let f = label.attr(satFor) - if f != "": - let elem = label.document.getElementById(f) - #TODO the supported check shouldn't be needed, just labelable - if elem of FormAssociatedElement and elem.tagType in LabelableElements: - return FormAssociatedElement(elem) - return nil - for elem in label.elements(LabelableElements): - if elem of FormAssociatedElement: #TODO remove this - return FormAssociatedElement(elem) - return nil - return nil +# <base> +proc href(base: HTMLBaseElement): string {.jsfget.} = + #TODO with fallback base url + let url = parseURL(base.attr(satHref)) + if url.isSome: + return $url.get + return "" -func form(label: HTMLLabelElement): HTMLFormElement {.jsfget.} = - let control = label.control - if control != nil: - return control.form - return nil +# <button> +func jsForm(this: HTMLButtonElement): HTMLFormElement {.jsfget: "form".} = + return this.form -# <link> -proc setRelList(link: HTMLLinkElement; s: string) {.jsfset: "relList".} = - link.attr(satRel, s) +proc setType(this: HTMLButtonElement; s: string) {.jsfset: "type".} = + this.attr(satType, s) # <form> +func canSubmitImplicitly*(form: HTMLFormElement): bool = + const BlocksImplicitSubmission = { + itText, itSearch, itURL, itTel, itEmail, itPassword, itDate, itMonth, + itWeek, itTime, itDatetimeLocal, itNumber + } + var found = false + for control in form.controls: + if control of HTMLInputElement: + let input = HTMLInputElement(control) + if input.inputType in BlocksImplicitSubmission: + if found: + return false + found = true + elif control.isSubmitButton(): + return false + return true + proc setRelList(form: HTMLFormElement; s: string) {.jsfset: "relList".} = form.attr(satRel, s) @@ -2999,6 +2923,101 @@ proc setValue(this: HTMLInputElement; value: string) {.jsfset: "value".} = proc setType(this: HTMLInputElement; s: string) {.jsfset: "type".} = this.attr(satType, s) +func checked*(input: HTMLInputElement): bool {.inline.} = + return input.internalChecked + +proc setChecked*(input: HTMLInputElement; b: bool) {.jsfset: "checked".} = + if input.inputType == itRadio: + for radio in input.radiogroup: + radio.invalidDeps.incl(dtChecked) + radio.internalChecked = false + radio.setInvalid() + input.invalidDeps.incl(dtChecked) + input.internalChecked = b + input.setInvalid() + +func inputString*(input: HTMLInputElement): string = + case input.inputType + of itCheckbox, itRadio: + if input.checked: + "*" + else: + " " + of itSearch, itText, itEmail, itURL, itTel: + input.value.padToWidth(int(input.attrulgz(satSize).get(20))) + of itPassword: + '*'.repeat(input.value.len).padToWidth(int(input.attrulgz(satSize).get(20))) + of itReset: + if input.attrb(satValue): + input.value + else: + "RESET" + of itSubmit, itButton: + if input.attrb(satValue): + input.value + else: + "SUBMIT" + of itFile: + let s = if input.file != nil: input.file.name else: "" + s.padToWidth(int(input.attrulgz(satSize).get(20))) + else: input.value + +# <label> +func control*(label: HTMLLabelElement): FormAssociatedElement {.jsfget.} = + let f = label.attr(satFor) + if f != "": + let elem = label.document.getElementById(f) + #TODO the supported check shouldn't be needed, just labelable + if elem of FormAssociatedElement and elem.tagType in LabelableElements: + return FormAssociatedElement(elem) + return nil + for elem in label.elements(LabelableElements): + if elem of FormAssociatedElement: #TODO remove this + return FormAssociatedElement(elem) + return nil + return nil + +func form(label: HTMLLabelElement): HTMLFormElement {.jsfget.} = + let control = label.control + if control != nil: + return control.form + return nil + +# <link> +proc setRelList(link: HTMLLinkElement; s: string) {.jsfset: "relList".} = + link.attr(satRel, s) + +# <option> +# https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-disabled +func isDisabled*(option: HTMLOptionElement): bool = + if option.parentElement of HTMLOptGroupElement and + option.parentElement.attrb(satDisabled): + return true + return option.attrb(satDisabled) + +func text(option: HTMLOptionElement): string {.jsfget.} = + var s = "" + for child in option.descendants: + let parent = child.parentElement + if child of Text and (parent.tagTypeNoNS != TAG_SCRIPT or + parent.namespace notin {Namespace.HTML, Namespace.SVG}): + s &= Text(child).data + return s.stripAndCollapse() + +func value*(option: HTMLOptionElement): string {.jsfget.} = + if option.attrb(satValue): + return option.attr(satValue) + return option.text + +proc setValue(option: HTMLOptionElement; s: string) {.jsfset: "value".} = + option.attr(satValue, s) + +func select*(option: HTMLOptionElement): HTMLSelectElement = + for anc in option.ancestors: + if anc of HTMLSelectElement: + return HTMLSelectElement(anc) + return nil + # <select> func jsForm(this: HTMLSelectElement): HTMLFormElement {.jsfget: "form".} = return this.form @@ -3110,53 +3129,6 @@ proc showPicker(this: HTMLSelectElement): Err[DOMException] {.jsfunc.} = #TODO add, remove -# <option> -# https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-disabled -func isDisabled*(option: HTMLOptionElement): bool = - if option.parentElement of HTMLOptGroupElement and - option.parentElement.attrb(satDisabled): - return true - return option.attrb(satDisabled) - -func text(option: HTMLOptionElement): string {.jsfget.} = - var s = "" - for child in option.descendants: - let parent = child.parentElement - if child of Text and (parent.tagTypeNoNS != TAG_SCRIPT or - parent.namespace notin {Namespace.HTML, Namespace.SVG}): - s &= Text(child).data - return s.stripAndCollapse() - -func value*(option: HTMLOptionElement): string {.jsfget.} = - if option.attrb(satValue): - return option.attr(satValue) - return option.text - -proc setValue(option: HTMLOptionElement; s: string) {.jsfset: "value".} = - option.attr(satValue, s) - -# <button> -func jsForm(this: HTMLButtonElement): HTMLFormElement {.jsfget: "form".} = - return this.form - -proc setType(this: HTMLButtonElement; s: string) {.jsfset: "type".} = - this.attr(satType, s) - -# <textarea> -func jsForm(this: HTMLTextAreaElement): HTMLFormElement {.jsfget: "form".} = - return this.form - -# <video> -func getSrc*(this: HTMLElement): tuple[src, contentType: string] = - let src = this.attr(satSrc) - if src != "": - return (src, "") - for el in this.elements(TAG_SOURCE): - let src = el.attr(satSrc) - if src != "": - return (src, el.attr(satType)) - return ("", "") - # <table> func caption(this: HTMLTableElement): Element {.jsfget.} = return this.findFirstChildOf(TAG_CAPTION) @@ -3341,6 +3313,35 @@ func sectionRowIndex(this: HTMLTableRowElement): int {.jsfget.} = return HTMLTableSectionElement(parent).rows.findNode(this) return -1 +# <textarea> +func jsForm(this: HTMLTextAreaElement): HTMLFormElement {.jsfget: "form".} = + return this.form + +func textAreaString*(textarea: HTMLTextAreaElement): string = + result = "" + let split = textarea.value.split('\n') + let rows = int(textarea.attrul(satRows).get(1)) + for i in 0 ..< rows: + let cols = int(textarea.attrul(satCols).get(20)) + if cols > 2: + if i < split.len: + result &= '[' & split[i].padToWidth(cols - 2) & "]\n" + else: + result &= '[' & ' '.repeat(cols - 2) & "]\n" + else: + result &= "[]\n" + +# <video> +func getSrc*(this: HTMLElement): tuple[src, contentType: string] = + let src = this.attr(satSrc) + if src != "": + return (src, "") + for el in this.elements(TAG_SOURCE): + let src = el.attr(satSrc) + if src != "": + return (src, el.attr(satType)) + return ("", "") + func newText*(document: Document; data: string): Text = return Text( internalDocument: document, |