I'm sending dom.apl from my apl-library (it needs work) together with
the libraries it copys. The function dom∆parse is slow and a memory
hog. You probably should say it should be rewritten in C and I would
agree. Each time I've thought about it, I laid down, put a hot compress
over my eyes until the thought went away.
w
#! /usr/local/bin/apl --script
⍝ Rewrite dom∆parse∆openElm to deal with closed element tags <tag/>
⍝ ********************************************************************
⍝ 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 <http://www.gnu.org/licenses/>.
⍝ ********************************************************************
)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,'</',(dom∆node∆nodeName node),'>'
→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←'<?',(dom∆pi∆target node),' ',(dom∆pi∆data node),'?>'
→0
com: ⍝ Comment Node
xml←'<!--',(dom∆node∆nodeValue node),'-->'
→0
doc: ⍝ Document node
xml← ∊dom∆node∆toxml ¨ dom∆node∆children node
→0
type: ⍝ Document Type node
xml←'<!DOCTYPE ',(dom∆node∆nodeName node)
→(~(2⊃node) lex∆haskey 'SYSTEM')/typePublic
xml←xml,' SYSTEM ',(2⊃node)lex∆lookup 'SYSTEM'
typePublic:
→(~(2⊃node) lex∆haskey 'PUBLIC')/typeEnd
xml←xml,' PUBLIC ',(2⊃node)lex∆lookup 'PUBLIC'
typeEnd:
xml←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 <tag/>
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←(∧/'<!-'=3↑src),(∧/'<!D'=3↑src),(∧/'</'=2↑src),(∧/'<?'=2↑src),('<'=1↑src),1
→b/(commentNode,doctypeNode,closeElm,proc,openElm,txtNode)
commentNode:
node←dom∆parse∆commentNode src
→end
doctypeNode:
node←dom∆parse∆doctypeNode src
→end
proc:
node←dom∆parse∆processingInstruction src
→end
openElm:
node←dom∆parse∆openElm src
→end
closeElm:
node←dom∆parse∆closeElm src
→end
txtNode:
node←dom∆createTextNode src
end:
∇
∇node←dom∆parse∆commentNode source
⍝⍝ Function creates a comment node from the source.
node←dom∆createComment 3↓source
∇
∇node←dom∆parse∆doctypeNode source
⍝⍝ Function creates a doctype node from source
node←dom∆createDocumentType 10↓source ⍝ 8 == ⍴ '<!DOCTYPE '
∇
∇node←dom∆parse∆processingInstruction source
⍝⍝ Function creates a processing instruction
node←dom∆createProcessingInstruction 2↓¯1↓source
∇
∇elm←dom∆parse∆openElm source;b;name;closed;attr;ix
⍝⍝ Function returns an element node from source.
name←1↓(b←∧\source≠' ')/source
name←(closed←'/'=¯1↑name)↓name
elm←dom∆createElement name
⍎closed/'elm←dom∆element∆childless elm'
→(∧/b)/ed
attr←'=' utl∆split_with_quotes ¨ ' ' utl∆split_with_quotes 1↓(~b)/source
ix←1
st:
→(ix>⍴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' '[email protected]'
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]
∇
#!/usr/local/bin/apl --script
⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝
⍝
⍝ lex 2016-08-29 15:55:56 (GMT-5)
⍝
⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝
⍝ Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
∇nx←lx lex∆assign item;ix;keys;keyShape
⍝⍝ Function to assign a value to a key where larg is the lexicon and
⍝⍝ rarg is a key value pair. Pair will be added if the key is not in
⍝⍝ the lexicon.
nx←lx
→(' '≠1↑0⍴⊃item[1])/err
⍝ Test for empty lexicon
→(0=1↑⍴nx)/add
keyShape←⍴keys←⊃nx[;1]
ix←(keys∧.=keyShape[2]↑⊃item[1])/⍳keyShape[1]
→(0=⍴ix)/add
replace:
nx[ix;2]←item[2]
→0
add:
nx←nx,[1]item
→0
err:
⍞←'Lexicon entries must use a character string as a key'
→0
∇
∇new←lex lex∆drop key;rlex;tbl
⍝⍝ Function to drop a key value pair
⍎(0=1↑⍴lex)/'new←lex◊→0'
rlex←(⍴tbl←⊃lex[;1])⌈0,⍴key←,key
new←(~(rlex↑tbl)∧.=rlex[2]↑key)⌿lex
∇
∇bool←lex lex∆haskey key;rkey;keys
⍝⍝ Function searches for key in lexicon lex and returns true if found.
⍎(0=1↑rkey←⍴keys←⊃lex[;1])/'bool←0◊→0'
rkey[2]←rkey[2]⌈⍴key←,key
bool←∨/(rkey↑keys)∧.=rkey[2]↑,key
∇
∇lex←lex∆init
⍝⍝ Function to initiate a lexicon
lex←0 2⍴''
∇
∇b←lex∆is lex
⍝⍝ Predicate returns true if argument is in fact a lexicon
⍎(2≠⍴⍴lex)/'b←0◊→0'
⍎(∧/0 2=⍴lex)/'b←1◊→0'
⍎(2>≡lex)/'b←0◊→0'
b←∧/' '=1↑0⍴⊃lex[;1]
∇
∇b←lex∆isempty lex
⍝⍝ Predicate returns true is the lexicon has no entries
b←∧/0 2=⍴lex
∇
∇keys←lex∆keys lx
⍝⍝ Function returns a list of keys in a lexicon
keys←lx[;1]
∇
∇value←lex lex∆lookup key;keyShape;keys;ix
⍝⍝ Function returns the value of key in a lexicon
keyShape←¯2↑0 0,⍴keys←⊃lex[;1]
⍎(0=ix←''⍴(keys∧.=keyShape[2]↑key)/⍳keyShape[1])/'value←''''◊→0'
value←⊃lex[ix;2]
∇
∇vals←lex∆values lx
⍝⍝ returns the valus in a lexicon
vals←lx[;2]
∇
∇new←lex lex∆from_alist list
⍝⍝ Function to generate a lexicon from a list
⎕es (0≠2|⍴list←,list)/'List must consist of name-value pairs.'
→(2=⎕nc 'lex')/recursion
lex←lex∆init
recursion:
new←lex
→(0=⍴list←,list)/0
new← (new lex∆assign 2↑list←,list) lex∆from_alist 2↓list
∇
∇Z←lex⍙metadata
Z←0 2⍴⍬
Z←Z⍪'Author' 'Bill Daly'
Z←Z⍪'BugEmail' '[email protected]'
Z←Z⍪'Documentation' 'eoc/apl-library.info'
Z←Z⍪'Download'
'https://sourceforge.net/p/apl-library/code/ci/master/tree/lex.apl'
Z←Z⍪'License' 'GPL v3.0'
Z←Z⍪'Portability' 'L1'
Z←Z⍪'Provides' ''
Z←Z⍪'Requires' ''
Z←Z⍪'Version' '0 1 2'
Z←Z⍪'Last update' '2019-07-01'
∇
#! /usr/local/bin/apl --script
⍝ ********************************************************************
⍝ $Id: $
⍝ $desc: Library of useful apl functions $
⍝ ********************************************************************
⍝ Util
⍝ Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
∇msg←utl∆helpFns fn;src;t
⍝⍝ Display help about a function
src←⎕cr fn
t←(+/∧\src=' ')⌽src
msg←(1,∧\'⍝'=1↓t[;1])⌿src
∇
∇msg←utl∆functionList prefix;list
⍝⍝ Display help for a list of functions whose name begins with
⍝⍝ prefix.
list←utl∆clean¨⊂[2](list[;⍳⍴prefix]∧.=prefix)⌿list←⎕nl 3
msg←{⎕tc,⎕tc,'∇',⎕tc[3] utl∆join ⊂[2]utl∆helpFns ⍵}¨list
∇
∇t←utl∆numberp v
⍝⍝ Is arg a number?
→(0=⍴t←''⍴0=⍴v)/0
⍎(1<≡v)/'t←0◊→0'
t←''⍴0=1↑0⍴v
∇
∇ t←utl∆stringp s
⍝⍝ Is arg a string?
⍝⍝ test for nested array
→(~t←1=≡s)/0
t←''⍴' '=1↑0⍴s←,s
∇
∇b←utl∆numberis tst
⍝⍝ Test whether a number can be obtained by executing a string
⍎(0=⍴tst←,tst)/'b←0 ◊ →0'
⍝⍝ Rotate spaces to right side
tst←(+/∧\tst=' ')⌽tst
⍝⍝ Test for spaces imbedded in numbers
→(~b←(+/∧\b)=+/b←tst≠' ')/0
b←(∧/tst∊' 1234567890-¯.')∧∨/0 1=+/tst='.'
b←b∧∧/~(1↓tst)∊'-¯'
∇
∇New←utl∆stripArraySpaces old;b
⍝⍝ Strips off leading and trailing spaces. Function operates on both
⍝⍝ vectors and arrays of rank 2. See also utl∆clean.
New←(+/∧\old=' ')⌽old
b←⌊/+/∧\⌽New=' '
→(V,M,E)[3⌊⍴⍴old]
⍝ Vector
V:
New←New[⍳-b-⍴New]
→0
⍝ Matrix
M:
New←New[;⍳-b-1↓⍴New]
→0
⍝ Error -- rank of old is too high
E:
⎕es 'Rank of array is too high'
∇
∇cl←utl∆clean txt;b;ix
⍝⍝ Converts all whites space to spaces and then removes duplicate
⍝⍝ spaces. See also utl∆stringArraySpaces.
txt←,txt
ix←(txt∊⎕tc,⎕av[10])/⍳⍴txt
txt[ix]←' '
→(0=⍴cl←(~(1⌽b)∧b←txt=' ')/txt)/0
cl←(cl[1]=' ')↓(-cl[⍴cl]=' ')↓cl
∇
∇o←k utl∆sub d
⍝⍝ Calculates subtotals for each break point in larg
o←+\[1]d
⍝ Test for rank of data
⎕es (~(⍴⍴d) ∊ 1 2)/'RANK ERROR'
→(V,A)[⍴⍴d]
⍝ Vectors
V:o←o[k]-¯1↓0,o[k]
→0
⍝ Arrays (of rank 2)
A: o←o[k;]-0,[1] o[¯1↓k;]
→0
∇
∇string←delim utl∆join vector
⍝⍝ Returns a character string with delim delimiting the items
⍝⍝ in vector.
string←1↓∊,delim,(⌽1,⍴vector)⍴vector
∇
∇v←delim utl∆split string;b;c
⍝⍝ Splits a character string into a nested vector of strings using
⍝⍝ delim as the delimiter.
→(1≠⍴delim←,delim)/many
→(∧/b←string ≠ delim)/last
→exit
many:
→(∧/~b←string∊delim)/last
string←(c←~b∧1⌽b)/string
b←c/~b
→exit
exit:
v←(⊂b/string),delim utl∆split 1↓(~b←∧\b)/string
→0
last:
v←1⍴⊂string
∇
∇v←delim utl∆split2 string;b
⍝⍝ Split a string at delim. No recursive algorithm
b←(delim=string)/⍳⍴string←,string
b←b,[1.1]-b-¯1+1↓b,1+⍴string
v←utl∆sph ¨ ⊂[2]b
∇
∇item←utl∆sph ix
⍝⍝ Helper function for utl∆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]
∇
∇ix← list utl∆listSearch item;rl;ri;l
⍝⍝ Search a character list for an item.
→(1=≡list)/arr
list←⊃list
arr:
⎕es(2≠⍴rl←⍴list)/'RANK ERROR'
ri←⍴item←,item
l←rl[2]⌈ri
→(0=⍴ix←(((rl[1],l)↑list)∧.=l↑,item)⌿⍳rl[1])/naught
ix←''⍴ix
→0
naught:
ix←1+''⍴⍴list
∇
∇ix←txt utl∆search word;⎕io;old∆io;ixx;bv
⍝⍝ Search for larg in rarg.
old∆io←⎕io
⎕io←0
ixx←⍳⍴txt←,txt
bv←(txt=1↑word←,word)∧ixx≤(⍴txt)-⍴word
ix←bv/ixx
ix←old∆io+(txt[ix∘.+⍳⍴word]∧.=word)/ix
∇
∇new←txt utl∆replace args;ix
⍝⍝ Search for and replace an item in rarg. Larg is a two element
⍝⍝ vector where Larg[1] is the text to search for, Larg[2] is the
⍝⍝ replacement text.
ix← txt utl∆search ⊃args[1]
new←((¯1+ix)↑txt),(,⊃args[2]),(¯1+(ix←''⍴ix)+⍴,⊃args[1])↓txt
∇
∇t←n utl∆execTime c;ts;lb;i
⍝⍝ Returns the number of milliseconds a command took. larg is the
⍝⍝ number of times to execute command. If larg is missing we execute
⍝⍝ once.
→(2=⎕nc 'n')/many
ts←⎕ts
⍎c
→ed
many:
lb←(n⍴st),ed
i←0
ts←⎕ts
st:
⍎c
→lb[i←i+1]
ed:
t←⎕ts
t←(60 1000⊥t[6 7])-60 1000⊥ts[6 7]
→0
∇
∇today←utl∆today
⍝⍝ Today's date as a string
today←'06/06/0000'⍕⎕ts[2 3 1]
∇
∇txt←utl∆lower m;ix
⍝⍝ Convert text to all lower case.
m←⎕ucs m←,m
ix←((m≥65)∧m≤90)/⍳⍴m
m[ix]←m[ix]+32
txt←⎕ucs m
∇
∇txt←utl∆upper m;ix
⍝⍝ Convert text to all upper case.
m←⎕ucs m←,m
ix←((m≥97)∧m≤122)/⍳⍴m
m[ix]←m[ix]-32
txt←⎕ucs m
∇
∇v←delim utl∆split_with_quotes string;b;c
⍝⍝ Split a string on a delimiter where some delimiter(s) may be
⍝⍝ inside quotes and therefore ignored.
delim←,delim
b←~(string∊delim)∧~≠\string='"'
v←(⊂c/string), ((~c)/b) utl∆swq_helper (~c←∧\b)/string
v←utl∆strip_quotes ¨ v
∇
∇v←b utl∆swq_helper string;c;d
⍝⍝ Helper function for utl∆split_with_quotes
→(0=+/~b)/end
d←~c←∧\1↓b
v←(⊂c/1↓string), (d/1↓b) utl∆swq_helper d/1↓string
→0
end:
v←0⍴0
∇
∇b←str1 utl∆stringEquals str2;l
⍝⍝ Compare two strings.
l←(⍴str1)⌈⍴str2
b←∧/(l↑str1)=l↑str2
∇
∇txt←utl∆crWithLineNo name;l
⍝⍝ Add line numbers to a character representation of a function.
l←¯1+1↑⍴txt←⎕cr name
txt←(' ∇',[1]'[000] '⍕⍪⍳l),txt
∇
∇clean←utl∆strip_quotes txt;bv
⍝⍝ Strip quotes from the start and end of character string.
clean←txt
→(~1↑bv←≠\clean∊'''"')/0
clean←(bv∧¯1⌽bv)/clean
∇
∇new←om utl∆round old
⍝⍝ Round numbers based on the Order of Magnitude. Left
⍝⍝ arg is thus a power of ten where positive numbers round to the
⍝⍝ left of the decimal point and negative to the right.
⍎(2≠⎕nc'om')/'om←0'
om←10*om
new←om×⌊.5+old÷om
∇
∇ar←utl∆concatColumns na
⍝⍝ Function returns a 2 dimensional text array from a nested array of text.
→(1=¯1↑⍴na)/lastCol
ar←(⊃na[;1]),' ', utl∆concatColumns 0 1↓na
→0
lastCol:
ar←⊃,na
→0
∇
∇n←utl∆convertStringToNumber s;bv;a
⍝⍝ Converts a vector of characters to a number. Function
⍝⍝ returns the original string when it fails in this attempt. For
⍝⍝ strings multiple numbers see utl∆import∆numbers.
→(~∧/s∊'0123456789.,+-¯ ()')/fail
→(1<+/s='.')/fail
→(0=⍴(s≠' ')/s)/fail
a←((~∧\bv)∧⌽~∧\⌽bv←s=' ')/s
→(0≠+/a=' ')/fail
→((∨/a='+')∧a[1]≠'+')/fail
→(∧/'-'=(' '≠a)/a)/zero
a[(a∊'(-')/⍳⍴a←,' ',a]←'¯'
n←⍎(~a∊'),')/a
→0
zero: ⍝ Excel sometimes uses dash for 0
n←0
→0
fail:
n←s
∇
∇n←utl∆import∆numbers s;bv
⍝⍝ Function to turn a column of figures (ie characters) into
⍝⍝ numbers. For a single number see util∆convertStringToNumber
⍎(2=≡s)/'s←⊃s'
bv←~∧/s=' '
s[(s∊'(-')/⍳⍴s←,' ',s]←'¯'
n←bv\⍎(~s∊'),')/s
∇
∇utl∆es msg
⍝⍝ Simulate an error. Similar to ⎕es with better control of the error
⍝⍝ message. Thanks JAS
→(0=⍴msg)/0
msg ⎕es 0 1
∇
∇b←list utl∆member item
⍝⍝ Tests whether a character vector is in list, a character array,
⍝⍝ or a nested list of strings.
b←(1+1↑⍴list)>list utl∆stringSearch item
∇
∇parsed←utl∆fileName∆parse fname;suffix
⍝⍝ Function breaks a fname down into three strings:
⍝⍝ 1) Path to directory
⍝⍝ 2) root name
⍝⍝ 3) suffix, that is whatever trails the final '.'.
parsed←'/' utl∆split fname
suffix←'.' utl∆split (⍴parsed)⊃parsed
→(one,many)[2⌊⍴suffix]
one:
parsed←(⊂'/' utl∆join ¯1↓ parsed),⊃suffix,⊂''
→0
many:
parsed←(⊂'/' utl∆join ¯1↓ parsed),(⊂'.'utl∆join ¯1↓suffix),¯1↑suffix
→0
∇
∇dir←utl∆fileName∆dirname parsed
⍝⍝ Function returns the directory portion of a parsed file name
dir←1⊃parsed
∇
∇base←utl∆fileName∆basename parsed
⍝⍝ Function returns the base of the file name from a parsed file name
base←2⊃parsed
∇
∇suffix←utl∆fileName∆suffixname parsed
⍝⍝ Function returns the suffix of a parsed file name.
suffix ← 3⊃parsed
∇
∇backup←utl∆fileName∆backupname parsed
⍝⍝ Function returns a name to which a file can be backed up.
backup←(1⊃parsed),'/',(2⊃parsed),'.bak'
∇
∇ar←utl∆concatColumns na
⍝⍝ Function returns a 2 dimensional text array from a nested array of text
→(1=¯1↑⍴na)/lastCol
ar←(⊃na[;1]),' ', utl∆concatColumns 0 1↓na
→0
lastCol:
ar←⊃,na
→0
∇
∇sub←breakFld utl∆breakon amts;ix
⍝⍝ function to calculate subtotals for changes in breakFld
ix←(~breakFld utl∆stringEquals ¨ 1⌽breakFld)/⍳⍴breakFld←,breakFld
sub←ix utl∆sub amts
∇
∇b←str utl∆stringMember list
⍝⍝ Function returns true if str is in list
b←∨/(⊂str) utl∆stringEquals ¨ list
∇
∇numbered←utl∆numberedArray array;shape;level
⍝⍝ Function prepends a line number on to an array
shape←⍴array
utl∆es ((0=level)∨(2≠⍴⍴array)∨2<level←≡array)/'Malformed array for these
purposes'
numbered←('[003] '⍕(shape[1],1)⍴⍳shape[1]),array
∇
∇ix←utl∆gradeup data;t1;base
⍝⍝ Function to alphabetically grade up data
⍎(∧/(2=≡data),t1←utl∆stringp ¨ data)/'data←⊃data'
utl∆es (~∧/t1)/'DATA NOT CHARACTERS'
base←2*⍳20
base←base[+/1,∧\base<⌈/⎕ucs ∊,data]
ix←⍋(⊂(¯1↑⍴data)⍴base)⊥¨⊂[2]⎕ucs¨data
∇
∇new ← utl∆sort old;base
⍝⍝ Function sorts a character array or nested character vectors
new←old[utl∆gradeup old]
∇
∇ix←data utl∆quad bottom_right;top_left;rows;cols;all
⍝⍝ Function returns the row and column indices defined by the top
⍝⍝ right and bottom left indices in the right argument
→(2≠⎕nc 'data')/syntax
→(1≠⍴⍴bottom_right)/syntax
⍝ →(2=⍴bottom_right)/nested
→(4≠⍴bottom_right)/syntax
top_left←bottom_right[1 2]
bottom_right←bottom_right[3 4]
→step2
nested:
top_left←1⊃bottom_right
bottom_right←2⊃bottom_right
→step2
step2:
→(~utl∆numberp top_left,bottom_right)/syntax
rows←((top_left[1]≤all)∧bottom_right[1]≥all)/all←⍳1↑⍴data
cols←((top_left[2]≤all)∧bottom_right[2]≥all)/all←⍳1↓⍴data
ix←(⊂rows),⊂cols
→0
syntax:
utl∆es 'SYNTAX IS: data_array utl∆quad upper_left, bottom_right'
∇
∇rs←delim utl∆bifurcate txt;bv
⍝⍝ Function breaks a string into two parts using delim.
bv←∧\txt≠⍬⍴delim
⍎((+/bv)=⍴txt)/'rs←txt ◊ →0'
rs←(⊂bv/txt),⊂(+/1,bv)↓txt
∇
∇wrpd←width utl∆wrap txt;curln;spaces;cut
⍝⍝ Function returns a multi-line text no wider the width
→(width≥⍴txt)/last
spaces←(txt=' ')/⍳⍴txt
cut←⌈/(spaces≤width)/spaces
wrpd←(⊂(cut-1)↑txt), width utl∆wrap cut↓txt
→0
last:
wrpd←⊂txt
∇
∇Z←utl⍙metadata
Z←0 2⍴⍬
Z←Z⍪'Author' 'Bill Daly'
Z←Z⍪'BugEmail' '[email protected]'
Z←Z⍪'Documentation' 'doc/apl-library.info'
Z←Z⍪'Download'
'https://sourceforge.net/p/apl-library/code/ci/master/tree/utl.apl'
Z←Z⍪'License' 'GPL v3.0'
Z←Z⍪'Portability' 'L1'
Z←Z⍪'Provides' ''
Z←Z⍪'Requires' ''
Z←Z⍪'Version' '0 1 4'
Z←Z⍪'Last update' '2019-07-01'
∇
#! /usr/local/bin/apl --script
⍝ ********************************************************************
⍝ stack.apl implement a stack in apl; You're just lucky I didn't name
⍝ them cons, car and cdr.
⍝ 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 <http://www.gnu.org/licenses/>.
⍝ ********************************************************************
∇stack←st∆init
⍝ Function sets up the stack
stack←⍬
∇
∇stack←old st∆push item
stack←(⊂item),old
∇
∇item←st∆peek stack
item←1⊃stack
∇
∇stack←old st∆poke item
⍝ Function to replace the top of the stack
stack←old
stack[1]←⊂item
∇
∇stack←st∆pop old
⍎(0=⍴old)/'stack←old◊→0'
stack←1↓old
∇
∇len←st∆length stack
len←⍴stack
∇
∇item←stack st∆nth ix;l
⍝ Function returns the nth item on the stack
utl∆es (ix > l←st∆length stack)/'PEEKING PAST END OF STACK'
item ←ix⊃stack
∇
∇Z←st⍙metadata
Z←0 2⍴⍬
Z←Z⍪'Author' 'Bill Daly'
Z←Z⍪'BugEmail' '[email protected]'
Z←Z⍪'Documentation' 'doc/apl-library.info'
Z←Z⍪'Download' 'sourceforge.net/projects/apl-library/'
Z←Z⍪'License' 'GPL v3'
Z←Z⍪'Portability' 'L1'
Z←Z⍪'Provides' 'st'
Z←Z⍪'Requires' ''
Z←Z⍪'Version' '0 0 1'
∇