#! /usr/local/bin/apl --script ⍝ Rewrite dom∆parse∆openElm to deal with closed element tags ⍝ ******************************************************************** ⍝ dom.apl Partial implementation of the Document Object Model ⍝ Copyright (C) 2019 Bill Daly ⍝ This program is free software: you can redistribute it and/or modify ⍝ it under the terms of the GNU General Public License as published by ⍝ the Free Software Foundation, either version 3 of the License, or ⍝ (at your option) any later version. ⍝ This program is distributed in the hope that it will be useful, ⍝ but WITHOUT ANY WARRANTY; without even the implied warranty of ⍝ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ⍝ GNU General Public License for more details. ⍝ You should have received a copy of the GNU General Public License ⍝ along with this program. If not, see . ⍝ ******************************************************************** )copy 1 utl )copy 1 lex )copy 1 stack ⍝ ******************************************************************** ⍝ ⍝ Create objects ⍝ ⍝ ******************************************************************** ∇attr←parent dom∆createAttribute name;value ⍝⍝ Function creates an element attribute →(2=⍴name)/double utl∆es (~utl∆stringp name)/name,' is not a valid attribute name.' →single single: value←dom∆TRUE →make double: utl∆es (~∧/(2=⍴name),utl∆stringp ¨ name)/'''',name,''' is not a valid attribute' value←2⊃name name←1⊃name make: attr←dom∆createNode name attr[2]←⊂(2⊃attr) lex∆assign 'nodeType' dom∆ATTRIBUTE_NODE attr[2]←⊂(2⊃attr) lex∆assign 'nodeValue' value ∇ ∇node← dom∆createComment txt node← dom∆createNode 'Comment' node[2]←⊂(⊃node[2]) lex∆assign 'nodeType' dom∆COMMENT_NODE node[2]←⊂(⊃node[2]) lex∆assign 'nodeValue' txt ∇ ∇docNode← dom∆createDocument rootName;rootNode;typeNode;uri;dn ⍝⍝ Function to create a document. If root name is a nested vector ⍝⍝ rootName[1] is the document qualifiedName and rootName[2] is its ⍝⍝ URI. Left argument docType is optional and if ommitted will be deduced. docNode← dom∆createNode 'Document' docNode←docNode dom∆node∆setNodeType dom∆DOCUMENT_NODE →0 ∇ ∇documentTypeNode← dom∆createDocumentType rootName;dt →((2=≡rootName)∧2=⍴rootName)/create rootName←' ' utl∆split rootName create: documentTypeNode←dom∆createNode ⊃rootName[1] dt←(⊃documentTypeNode[2]) lex∆assign 'nodeType' dom∆DOCUMENT_TYPE_NODE dt←dt lex∆assign (⊂'nodeValue'), ⊂rootName[1] →(1=⍴rootName)/end dt←dt lex∆assign 2↑1↓rootName,dom∆TRUE end: documentTypeNode[2]←⊂dt →0 ∇ ∇elementNode← dom∆createElement name;en elementNode← dom∆createNode name en←(⊃elementNode[2]) lex∆assign 'nodeType' dom∆ELEMENT_NODE elementNode[2]←⊂en lex∆assign 'attributes' dom∆createNamedNodeMap ∇ ∇node←dom∆createTextNode txt;attrs node← dom∆createNode '#text#' attrs←(⊃node[2]) lex∆assign 'nodeType' dom∆TEXT_NODE node[2]←⊂attrs lex∆assign 'nodeValue' txt ∇ ∇pi←dom∆createProcessingInstruction txt;b;target;data;pn ⍝⍝ Function creates processor specific instructions node txt←utl∆clean txt target←(b←∧\txt≠' ')/txt data←1↓(~b)/txt pi← dom∆createNode target pn←(⊃pi[2]) lex∆assign 'nodeType' dom∆PROCESSING_INSTRUCTION_NODE pn←pn lex∆assign 'target' target pi[2]←⊂pn lex∆assign 'data' data ∇ ∇node← dom∆createNode name ⍝⍝ Fn creates a DOM node node←lex∆init node←node lex∆assign 'nodeName' name node←node lex∆assign 'nodeValue' ' ' node←node lex∆assign 'nodeType' 0 node←(⊂0⍴0),⊂node ∇ ⍝ ******************************************************************** ⍝ ⍝ Node Methods ⍝ ⍝ ******************************************************************** ∇new←node dom∆node∆appendChild child;children ⍝⍝ Function to add a child to the end of our vector new←node children←(⊃node[1]),⊂child new[1]←⊂children ∇ ∇new←node dom∆node∆prependChild child; children ⍝⍝ Function to add a child tot he begining of our vector new←node children←(⊂child),1⊃node new[1]←⊂children ∇ ∇n←dom∆node∆nodeName node n←(⊃node[2])lex∆lookup 'nodeName' ∇ ∇new←node dom∆node∆setNodeName name new←node[1],⊂(⊃node[2]) lex∆assign 'nodeName' name ∇ ∇t←dom∆node∆nodeType node t←(⊃node[2]) lex∆lookup 'nodeType' ∇ ∇new←node dom∆node∆setNodeType type new←node[1],⊂(⊃node[2]) lex∆assign 'nodeType' type ∇ ∇v←dom∆node∆nodeValue node v←(⊃node[2])lex∆lookup 'nodeValue' ∇ ∇new←node dom∆node∆setNodeValue value new←node[1],⊂(⊃node[2]) lex∆assign 'nodeValue' value ∇ ∇o←dom∆node∆ownerDocument node o←(⊃node[2]) lex∆lookup 'ownerDocument' ∇ ∇new←node dom∆node∆setOwenerDocument doc new←node[1],⊂(⊃node[2]) lex∆assign 'ownerDocument' doc ∇ ∇ch←dom∆node∆children node ch←⊃node[1] ∇ ∇b←dom∆node∆hasChildren node b←0≠1↑⍴1⊃node ∇ ∇b←dom∆node∆hasAttributes node b←~lex∆isempty dom∆node∆attributes node ∇ ∇new←node dom∆node∆setChildren children ⍝⍝ Out with the old in with the new. This function replaces what ⍝⍝ ever children there are with an new list. new←(⊂children),node[2] ∇ ∇attrs←dom∆node∆attributes node ⍝⍝ Function returns a named node map of attributes attrs←(⊃node[2]) lex∆lookup 'attributes' ∇ ∇new←node dom∆node∆setAttribute item;attr;cix;attr_vector →(dom∆attr∆predicate item)/setAttr item←dom∆createAttribute item setAttr: attr←dom∆node∆attributes node attr←attr dom∆namedNodeMap∆setNamedItem item node[2]←⊂(2⊃node) lex∆assign 'attributes' attr new←node ∇ ∇xml←dom∆node∆toxml node;next;nextix ⍝⍝ Function returns an xml text vector for a node →(elm,attr,txt,cdata,ref,ent,pi,com,doc,type,frag,note)[dom∆node∆nodeType node] elm: ⍝ Element xml←'<',(dom∆node∆nodeName node) ⍎(dom∆node∆hasAttributes node)/'xml←xml,dom∆node∆toxml ¨ dom∆namedNodeMap∆list dom∆node∆attributes node' ⍎(~dom∆node∆hasChildren node)/'xml←xml,''/>''◊→0' xml←xml,'>' xml←xml,∊dom∆node∆toxml ¨ dom∆node∆children node xml←xml,'' →0 attr: ⍝ Attribute →(dom∆TRUE utl∆stringEquals dom∆node∆nodeValue node)/single_attr double_attr: xml←' ',(dom∆node∆nodeName node),'="',(dom∆node∆nodeValue node),'"' →0 single_attr: xml←' ',dom∆node∆nodeName node →0 txt: ⍝ Text xml←dom∆node∆nodeValue node →0 cdata: ⍝ CDATA xml←dom∆node∆nodeValue node →0 ref: ⍝ Entity Reference xml←'NOT IMPLEMENTED' →0 ent: ⍝ Entity xml←'NOT IMPLEMENTED' →0 pi: ⍝ Processing Instruction xml←'' →0 com: ⍝ Comment Node xml←'' →0 doc: ⍝ Document node xml← ∊dom∆node∆toxml ¨ dom∆node∆children node →0 type: ⍝ Document Type node xml←'' →0 frag: ⍝ Document fragment xml←'NOT IMPLEMENTED' →0 note: ⍝ Notation xml←'NOT IMPLEMENTED' →0 ∇ ∇child←node dom∆node∆getChild n ⍝⍝ Returns the nth child of node child←⊃(dom∆node∆children node)[n] ∇ ∇children← dom∆node∆getChildren node children←1⊃node ∇ ∇b←dom∆node∆predicate node ⍝⍝ Function tests to see if node is a dom node. This is not conical, ⍝⍝ but I can't proceed without it. →(~b←1=⍴⍴node)/0 →(~b←2=⍴node)/0 →(~b←lex∆is 2⊃node)/0 →(~b←(2⊃node) lex∆haskey 'nodeName') →(~b←(2⊃node) lex∆haskey 'nodeValue') →(~b←(2⊃node) lex∆haskey 'nodeType') b←1 ∇ ⍝ ******************************************************************** ⍝ ⍝ Element Methods ⍝ ⍝ ******************************************************************** ∇new←dom∆element∆childless elm ⍝⍝ Method marks an element as childless ie new←elm new[2]←⊂(2⊃new) lex∆assign 'childless' 1 ∇ ∇b←dom∆element∆isChildless elm ⍝⍝ Method returns the childless attribute b←(2⊃elm) lex∆lookup 'childless' ∇ ⍝ ******************************************************************** ⍝ ⍝ Document methods ⍝ ⍝ ******************************************************************** ∇node←dom∆document∆rootElement doc;children;i;lb ⍝⍝ Function returns the root element of a document children←dom∆node∆children doc i←1 lb←((⍴children)⍴st),ed st: node←⊃children[i] →(dom∆ELEMENT_NODE=dom∆node∆nodeType node)/0 →lb[i←i+1] ed: node←dom∆createElement 'MALFORMED DOCUMENT' →0 ∇ ∇doc←doc dom∆document∆setRootElement rootElm;children;i;lb ⍝⍝ Function replaces the root element of a document. Function should ⍝⍝ be called after updating or changing nodes of a document. i←1 lb←((⍴children←⊃doc[1])⍴st),ed st: →(~dom∆ELEMENT_NODE=dom∆node∆nodeType ⊃children[i])/next children[i]←⊂rootElm next: →lb[i←i+1] ed: doc[1]←⊂children ∇ ∇type←dom∆document∆getDocumentType doc;children ⍝⍝ Function returns the document type node. children←dom∆node∆getChildren doc type←(dom∆DOCUMENT_TYPE_NODE = dom∆node∆nodeType¨children)/children ∇ ∇doc←doc dom∆document∆setDocumentType typeNode;children;i;lb ⍝⍝ Function replaces the root element of a document. Function should ⍝⍝ be called after updating or changing nodes of a document. i←1 lb←((⍴children←⊃doc[1])⍴st),ed st: →(~dom∆DOCUMENT_TYPE_NODE=dom∆node∆nodeType ⊃children[i])/next children[i]←⊂typeNode next: →lb[i←i+1] ed: doc[1]←⊂children ∇ ∇nl←name dom∆document∆getElementsByTagName node;children;child;lb ⍝⍝ Function returns a NodeList of elements with the give name →(name utl∆stringEquals dom∆node∆nodeName node)/ahit nl←⊂dom∆createNodeList →ch ahit: nl←(⊂node),dom∆createNodeList →ch ch: →(0=⍴children←dom∆node∆getChildren node)/0 child←1 lb←((⍴children)⍴st),end st: nl←nl,name dom∆document∆getElementsByTagName child⊃children nl←(0≠∊⍴¨nl)/nl →lb[child←child+1] end: ∇ ⍝ ******************************************************************** ⍝ ⍝ Attribute Methods ⍝ ⍝ ******************************************************************** ∇ b←dom∆attr∆predicate node →(~b←dom∆node∆predicate node)/0 b←dom∆ATTRIBUTE_NODE = dom∆node∆nodeType node ∇ ⍝ ******************************************************************** ⍝ ⍝ Nodelist Methods ⍝ ⍝ ******************************************************************** ∇nl←dom∆createNodeList nl←0⍴0 ∇ ∇length←dom∆nodeList∆length list length←''⍴⍴list ∇ ∇node←list dom∆nodeList∆item item ⍝⍝ Returns the itemth ⍎(item>⍴list)/'item←0⍴0 ◊ →0' node←item⊃list ∇ ∇new←list dom∆nodeList∆appendNode node ⍝⍝ Function appends a node to a node list →(0≠⍴list)/append new←1⍴⊂node →0 append: new←list,⊂node ∇ ∇ix←nodeList dom∆nodeList∆lookup name ⍝⍝ Function returns the index of the given node name in a node list. ix←(dom∆node∆nodeName ¨ nodeList) utl∆listSearch name ∇ ∇b←dom∆nodeList∆predicate list ⍝⍝ Function test whether list is a nodeList →(~b←1=⍴⍴list)/0 ⍝ Not a list b←∧/dom∆node∆predicate ¨ list ∇ ⍝ ******************************************************************** ⍝ ⍝ NamedNodeMap ⍝ ⍝ ******************************************************************** ∇ map←dom∆createNamedNodeMap map←lex∆init ∇ ∇ node←map dom∆namedNodeMap∆getNamedItem name node←map lex∆lookup name ∇ ∇ map←old dom∆namedNodeMap∆setNamedItem attr;name ⍝⍝ Function to add or change an attribute name←dom∆node∆nodeName attr map←old lex∆assign name attr ∇ ∇ map←old dom∆namedNodeMap∆removeNamedItem attr;name ⍝⍝ Function to remove an attribute name←dom∆node∆nodeName attr map←old lex∆drop name ∇ ∇item←map dom∆namedNodeMap∆item index ⍝⍝ Function returns the indexth item in the map item←2⊃map ∇ ∇list←dom∆namedNodeMap∆list map ⍝⍝ Function returns the elements of the map as a list list←lex∆values map ∇ ⍝ ******************************************************************** ⍝ ⍝ Processing instructions are dom∆pi ⍝ ⍝ ******************************************************************** ∇target←dom∆pi∆target node target←(⊃node[2]) lex∆lookup 'target' ∇ ∇data←dom∆pi∆data node data←(⊃node[2]) lex∆lookup 'data' ∇ ⍝ ******************************************************************** ⍝ ⍝ Parse Methods ⍝ ⍝ ******************************************************************** ∇doc←dom∆parse txt;nl ⍝⍝ Function to parse an xml text buffer nl←dom∆parse∆nodeFromSource ¨ '>' dom∆split txt doc←dom∆parse∆foldNodeList nl ∇ ∇node←dom∆parse∆nodeFromSource src;b ⍝⍝ dom∆parse subroutine returns a node list from the source once it ⍝⍝ has been split on '>' →(0=⍴src)/txtNode src←utl∆clean src b←(∧/'⍴attr)/ed elm←elm dom∆node∆setAttribute ix⊃attr ix←ix+1 →st ed: ∇ ∇node←dom∆parse∆closeElm source ⍝⍝ Function returns a place holder from the end of an element. node←dom∆createElement 1↓source node←node dom∆node∆setNodeValue 'Closing element' node←node dom∆node∆setNodeType dom∆special_ELEMENT_END ∇ ∇doc←dom∆parse∆foldNodeList nl;curNode;nlix;nodeStack;b;docNode ⍝⍝ Function traverses node list nl finding children and assigning ⍝⍝ them to their parent. doc←dom∆createDocument '#Document' nodeStack← st∆init nlix←1 ⍝ First loop to find the root element st1: curNode←nlix⊃nl ⍎(dom∆DOCUMENT_TYPE_NODE=dom∆node∆nodeType curNode)/'doc←doc dom∆node∆appendChild curNode ◊ →nxt1' ⍎('xml' utl∆stringEquals dom∆node∆nodeName curNode)/'doc←doc dom∆node∆appendChild curNode ◊ →nxt1' →(dom∆ELEMENT_NODE ≠ dom∆node∆nodeType curNode)/nxt1 nodeStack←nodeStack st∆push doc nodeStack←nodeStack st∆push curNode nxt1: nlix←nlix+1 →(2=st∆length nodeStack)/st2 →st1 ⍝ Second loop to find the children of the root element st2: curNode←nlix⊃nl →(dom∆ELEMENT_NODE dom∆special_ELEMENT_END dom∆TEXT_NODE = dom∆node∆nodeType curNode)/elm,elmEnd,txt ⍝ What do I do now? →nxt2 elm: nodeStack←nodeStack dom∆parse∆foldElement curNode →nxt2 elmEnd: nodeStack←nodeStack dom∆parse∆endElement curNode →nxt2 txt: nodeStack←nodeStack dom∆parse∆foldText curNode →nxt2 nxt2: nlix←nlix+1 →(nlix>⍴nl)/ed →st2 ed: doc←nodeStack st∆nth st∆length nodeStack st3: →(1=st∆length nodeStack)/0 curNode←st∆peek nodeStack nodeStack←st∆pop nodeStack doc←doc dom∆node∆appendChild curNode →st3 ∇ ∇ newStack←nodeStack dom∆parse∆foldText txt;words;tag;parent ⍝⍝ Function folds a text node into the nodeStack words←dom∆node∆nodeValue txt words←'<' utl∆split utl∆clean words →('/'=1↑tag←,⊃¯1↑words)/endFound newStack←nodeStack st∆push txt →0 endFound: tag←1↓tag ⍝ For '/' txt←txt dom∆node∆setNodeValue utl∆clean 1⊃words parent←st∆peek nodeStack ⍎(~tag utl∆stringEquals dom∆node∆nodeName parent)/'newStack←nodeStack st∆push txt ◊ →0' nodeStack←st∆pop nodeStack parent←parent dom∆node∆appendChild txt newStack←nodeStack st∆push parent →0 ∇ ∇ newStack←nodeStack dom∆parse∆foldElement elm;parent ⍝⍝ Function folds an element node into the nodelist newStack←nodeStack st∆push curNode ∇ ∇ nodeStack←nodeStack dom∆parse∆endElement curNode;ix;iy;child;name ⍝⍝ Routine to append children on stack to the current element node. name←('/'=name[1])↓name←dom∆node∆nodeName curNode →(dom∆element∆isChildless curNode)/0 ix←1 st1: →(name utl∆stringEquals dom∆node∆nodeName nodeStack st∆nth ix)/nxt ix←ix+1 →st1 nxt: →(ix=1)/ed child←st∆peek nodeStack nodeStack←st∆pop nodeStack ix←ix - 1 nodeStack[ix]←⊂(ix⊃nodeStack) dom∆node∆prependChild child →nxt ed: ∇ ⍝ ******************************************************************** ⍝ ⍝ Meta ⍝ ⍝ ******************************************************************** ∇Z←dom⍙metadata Z←0 2⍴⍬ Z←Z⍪'Author' 'Bill Daly' Z←Z⍪'BugEmail' 'address@hidden' Z←Z⍪'Documentation' 'doc/apl-library.info' Z←Z⍪'Download' 'https://sourceforge.net/projects/apl-library/files/latest/download?source=directory' Z←Z⍪'License' 'GPL' Z←Z⍪'Portability' 'L2' Z←Z⍪'Provides' 'dom' Z←Z⍪'Requires' 'util lex' Z←Z⍪'Version' '0 2 5' Z←Z⍪'Last update' '2019-07-01' ∇ dom∆ELEMENT_NODE←1 dom∆ATTRIBUTE_NODE←2 dom∆TEXT_NODE←3 dom∆CDATA_SECTION_NODE←4 dom∆ENTITY_REFERENCE_NODE←5 dom∆ENTITY_NODE←6 dom∆PROCESSING_INSTRUCTION_NODE←7 dom∆COMMENT_NODE←8 dom∆DOCUMENT_NODE←9 dom∆DOCUMENT_TYPE_NODE←10 dom∆DOCUMENT_FRAGMENT_NODE←11 dom∆NOTATION_NODE←12 dom∆special_ELEMENT_END←50 dom∆type∆DESC←12⍴0 dom∆type∆DESC[1]←⊂'Element' dom∆type∆DESC[2]←⊂'Attribute' dom∆type∆DESC[3]←⊂'Text' dom∆type∆DESC[4]←⊂'CDATA section' dom∆type∆DESC[5]←⊂'Entity reference' dom∆type∆DESC[6]←⊂'Entity' dom∆type∆DESC[7]←⊂'Processing instruction' dom∆type∆DESC[8]←⊂'Comment' dom∆type∆DESC[9]←⊂'Document' dom∆type∆DESC[10]←⊂'Document type' dom∆type∆DESC[11]←⊂'Document fragment' dom∆type∆DESC[12]←⊂'Notation' dom∆TRUE←'True' dom∆FALSE←'False' dom∆defaultImplementation←'THIS WORKSPACE' dom∆error∆NOT_FOUND←'NOT FOUND' ∇v←delim dom∆split string;b ⍝ Split a string at delim. No recursive algorithm for dom parsing. b←(delim=string)/⍳⍴string←,string b←b,[1.1]-b-¯1+1↓b,1+⍴string v←dom∆sph ¨ ⊂[2]b ∇ ∇item←dom∆sph ix ⍝ Helper function for dom∆split returns an item from a character ⍝ vector where ix index of the delimeter in the stringstring and the ⍝ length of the item. ix←ix[1]+⍳ix[2] item←string[ix] ∇