Embedding visual tutorial

Embedding visual tutorial

:Namespace MS3SiteUtils ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝
⍝ ┌─────────────────────────────────────────────────────────────────────────────────────────────┐ ⍝
⍝ │ This is the collection of utility functions used by MS3Server and the index page of the     │ ⍝
⍝ │ MiServer Sample Site, MS3, accessible at miserver.dyalog.com                                │ ⍝
⍝ ├─────────────────────────────────────────────────────────────────────────────────────────────┤ ⍝
⍝ │ The code is split up in six sections:                                                       │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ A_GENERAL has utilities that appropriate to APL or MiServer in general                │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ B_FILES has various tools to list, read, and cache files and their content            │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ C_STRINGS string and multi-string manipulation and extraction of information          │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ D_ELEMENTS aids to create elements for the HTML Document Object Model                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ E_TUTORIAL has subroutines for the Tutorial tab on the index page                     │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ F_CONSTANTS niladic functions that return unchanging values and cache                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ └─────────────────────────────────────────────────────────────────────────────────────────────┘ ⍝
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION A_GENERAL ⍝ UTILITY FUNCTIONS THAT ARE NOT SPECIFIC TO THE "MS3" SITE

    ç←{⊂⍣(1=≡,⍵)⊢⍵} ⍝ enclose if simple     Mnemonic for the V16 glyph

    FwSl←'[\\/]+'⎕R'/' ⍝ Make all slash-block into single forward-slashes

    Q←'"'∘,,∘'"' ⍝ Surround with quotes

    FromCSV←↑{'"'~¨⍨1↓¨t⊂⍨(','=t)∧~≠\'"'=t←',',⍵}¨ ⍝ Convert CSV VTV to matrix (no escaped chars)

      Section←{ ⍝ Extract section ⍺:: from code ⍵
          regex←'^\s*⍝\s*',⍺,':(:.*?)$((\R^⍝(?!\s*\w+::).*?$)*)' ⍝ find '  ⍝  LeftArg:: some text'
          opts←('Mode' 'M')('DotAll' 1)('ML' 1)                  ⍝ '^'≡linestart  EOL∊'.'  1st-only
          res←regex ⎕S'\1\2'⍠opts⊢⍵                              ⍝ return parts 1 and 2
          #.Strings.deb'⍝'~⍨1↓⊃res                               ⍝ strip spaces and lamp
      }

      Controls←{ ⍝ Extract names of controls used in sample ⍵
          (Words'Control'Section Read ⍵)~'_',¨NSS
      }

    Of←{⍵/⍨⍺⍺¨⍵} ⍝ Those elements of ⍵ that satisfy ⍺⍺ element

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION B_FILES ⍝ FUNCTIONS THAT READ FILES

    Score←{(102-2×(50↑⊃⌽⍵)⍳⊂⍺)-∨/'Advanced'⍷⊃⍵} ⍝ Relevance score on index of control in list

      List←{ ⍝ Files in site folder
          ⍺←1↓FILEEXT                                      ⍝ default .ext
          mods←' -rec -raw -full=2 -ext=',⍺                ⍝ subdirs no-format rooted spec-ext
          list←(⎕SE.SALT.List #.Boot.AppRoot,⍵,mods)[;1 2] ⍝ begin at application root (MS3/)
          f←~d←list[;1]≡¨⊂'<DIR>'                          ⍝ find dirs
          list←FwSl list[;2]                               ⍝ normalize slashes for WWW
          (d/list),←'/'                                    ⍝ mark dirs with final slashes
          (f/list),←⊂'.',⍺                                 ⍝ add .ext to files
          list↓¨⍨¯1+1⍳⍨(FwSl ⍵)⍷⊃list                      ⍝ trim
      }

    ∇ r←Read page;i ⍝ Read a page via the #.CACHE.read (C)
      :Trap 0
          :If (≢C.read.keys)≥i←C.read.keys⍳⊂page ⍝ is it cached, and if so, save position
              r←i⊃C.read.data                     ⍝ extract
          :Else
              C.read.data,←⊂r←#.Files.GetVTV #.Boot.AppRoot,page,(~'.'∊page)/FILEEXT
              C.read.keys,←⊂page
          :EndIf
      :Else
          r←,⊂'[failed to read "',page,'"]'
      :EndTrap
    ∇

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION C_STRINGS ⍝ MANIPULATION OF CHARACTER VECTORS AND VECTORS OF CHARACTER VECTORS

    Slash←'/'∘=⊂⊢

    Name←{1↓⊃⌽(Slash ⍵)~⊂,'/'}  ⍝ '/aaa/bbb/ccc/ddd/' →         'ddd' (only in BuildTree)

    Words←{(1↓¨(∊∘' .'⊂⊢)' ',⍵)~⊂''} ⍝ Split at spaces and dots and remove empty pieces

    NoExt←{'.'∊⍵:⍵↓⍨-'.'⍳⍨⌽⍵⋄⍵} ⍝ 'aaa.bbb.ccc'  →  'aaa.bbb'

    NoNL←'\n' '\r'⎕R' ' ''⍠'Mode' 'D' ⍝ Replace newlines with spaces

    LastSeg←{⍺←⊢ ⋄ 1↓⍺⊃⌽Slash '/',⍵} ⍝ ⍺th (default 1st) /-separated segment from the end

    Levels←{(+/¨'/'=⍵)+~'/'=(⊃⌽)¨⍵} ⍝ Number of /s adjusted for categories' final /

    Shuffle←(?⍨∘≢⊃¨⊂)⊢⊂⍨⊢≡¨1↑⊢ ⍝ Shuffle the root elements of an XML snippet

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION D_ELEMENTS ⍝ FUNCTIONS THAT AUTOMATE THE CREATION OF DOM ELEMENTS AND HTML CODE

      Over←{
          ⊂'',⍺,'
',('/'⎕R'
'⊢⍵),''
      }

      External←{ ⍝ Icon off-site link
          ⍺←''
      ⍝    '.external' 'target=_blank' 'data-dyalog-tip="External link"'New _.A((⍺,'🔗')⍵)
          '',⍺,'🔗'
      }

      DescrEmbed←{ ⍝ Link to and iframed page
          l←('Description'Section Read ⍵),'
'
          l,←'Open this '
          l,←'simple sample' 'advanced sample' 'mini-app'⊃⍨1⍳⍨∨/¨'Simple' 'Advanced'⍷¨⊂⍵
          l,←' in a new tab'
          e←''
          l e
      }

      Link←{ ⍝ New-tab link with optional (⍺) "tooltip"
          ⍺←0
          ⍝(('data-dyalog-tip=',Q ⍺){⍺ ⍵}⍣(⍺≢0)⊢'target="_blank"')New _.A ⍵
          '',(⊃⍵),''
      }

      DocLink←{ ⍝ Link to WidgetDoc with appropriate parameters
          link←DocUrl ⍵
          ctor←'ctor'ForControl ⍵
          0=⍴ctor::Link ⍵ link ⍝ upon fail to retrieve info
          ('Constructor: ',ctor)Link ⍵ link
      }

      DocUrl←{
          '/Documentation/DyalogAPIs/WidgetDoc?namespace=_',('ns'ForControl ⍵),'&widget=',⍵
      }

      DocTreeLink←{ ⍝ Link to file in doc folder
          '/'=⊃⌽⍵:''
          ('/Documentation',⍵)'target="_blank"'
      }

      ReadingTree←{ ⍝ Create tree of documentation links
          l←'/',¨⍵~⊂''
          (Name∘NoExt¨l)(Levels l)(DocTreeLink¨l)
      }

    ∇ r←RelDocs w;list;item ⍝ Links to related samples
      list←'relevant'ForControl w
      r←''
      :For item :In list
          r,←'
',(Link('filedescr'ForFile item)item),'

'
      :EndFor
      r↓⍨←¯5
    ∇

      FormatList←{ ⍝ List of category-title pairs
          ⍺←1↓FILEEXT
          (⍺≡1↓FILEEXT)CatAndItem¨{'/'≠⊃⌽⍵}Of ⍺ List ⍵
      }

      CatAndItem←{ ⍝ (category) (filename/description)
          cat←2 LastSeg ⍵
          cat←'General' 'Mini App'cat['Documentation' 'Applications'⍳⊂cat]
          cat(Link((NoExt LastSeg)⍣(~⍺)('Description'Section Read)⍣⍺⊢⍵)⍵)
      }

      FilterTable←{
          dt←⍺ New _.DataTable ⍵
          dt.Options.mark←_true                ⍝ highlight search-terms
          dt.Options.columns←⎕NS¨4⍴⊂⍬          ⍝ options per column
          dt.Options.columns[4].visible←_false ⍝ hide column4
          dt.Options.dom←'l<"#fltcnt">ftpr'    ⍝ insert div with id "fltcnt" into table header
          dt.Plugins←'yadcf'
          dt.Options.yadcf←⎕NS ⍬
          dt.Options.yadcf.Filters←⎕NS ⍬
          dt.Options.yadcf.Filters.(column_number select_type filter_container_id)←3 'chosen' 'fltcnt'
          dt.Options.yadcf.Filters.(filter_type select_type_options filter_default_label)←'multi_select'(⊂'{width:"30em"}')'Filter by library'
          dt.Options.yadcf.Filters/⍨←1 ⍝ needs array of namespaces
          dt
      }

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION E_TUTORIAL ⍝ FUNCTIONS USED BY THE index PAGE'S Tutorial TAB

    ∇ t←Elements;tabs;r;sp;fs;ig;colors;rgbs;button;p;cbs;t2;g
      t←'#div'New _.div'This div is the main container of this tab. ' ⍝ #div
      p←'#p't.Add _.p'This is the beginning of a paragraph, ' ⍝ #p
      '#span'p.Add _.span'but this phrase is a separate piece of text inside the paragraph. ' ⍝ #span
      button←'#Button't.Add _.Button'Button' ⍝ #Button
      sp←'#StackPanel't.Add _.StackPanel ⍝ #StackPanel
      sp.Add'StackPanel'
      '#EditField'sp.Add _.EditField'EditField' 'EditField' ⍝ #EditField
      '#Select'sp.Add _.Select'Red' 'Green' 'Blue' ⍝ #Select
      '#Input1't.Add _.Input'number' 426 ' Number Input: ' ⍝ #Input1
      t.Add _.br
      fs←'#Fieldset1't.Add _.Fieldset'CheckBoxes' ⍝ #Fieldset1
      cbs←{'.CheckBox'New _.CheckBox ⍵}¨'checked' 'unchecked' ⍝ .CheckBox
      ig←'#InputGrid'fs.Add _.InputGrid('Checked' 'Unchecked')cbs ⍝ #InputGrid
      ig.Flip←1 ⍝ #InputGrid
      fs←'#Fieldset2't.Add _.Fieldset'RadioButtonGroup' ⍝ #Fieldset2
      '#RadioButtonGroup'fs.Add _.RadioButtonGroup('Selected' 'Unselected')1 'right' ⍝ #RadioButtonGroup
      '#h5't.Add _.h5'Slider' ⍝ #h52
      '#Input2't.Add _.Input'range' 1 ⍝ #Input2
      '#List't.Add _.List('Primaries' 'Cyan' 'Magenta' 'Yellow' 'Key' 'Black')(1 2 2 2 1 2) ⍝ #List
      colors←'aqua' 'black' 'blue' 'fuchsia' 'gray' 'green' 'lime' 'maroon' ⍝ #Table
      rgbs←'#00FFFF' '#000000' '#0000FF' '#FF00FF' '#808080' '#008000' '#00FF00' '#800000' ⍝ #Table
      colors,←'navy' 'olive' 'purple' 'red' 'silver' 'teal' 'white' 'yellow' ⍝ #Table
      rgbs,←'#000080' '#808000' '#800080' '#FF0000' '#C0C0C0' '#008080' '#FFFFFF' '#FFFF00' ⍝ #Table
      '#Table't.Add _.Table('Color' 'RGB'⍪colors,⍪rgbs)'' 1 ⍝ #Table
      t.Add _.style ScriptFollows
      ⍝ #div {padding: 10px; margin: 10px;}
      ⍝ #Button {vertical-align: top; margin: 5px;}
      ⍝ #StackPanel {display: inline-block; padding: 5px;}
      ⍝ #Fieldset1 {display: inline-block;}
      ⍝ #Fieldset2 {display: inline-block;}
      ⍝ #h5 {margin: 1em 0 0.5em 0;}
      ⍝ #Input1 {width: 4em;}
      ⍝ #Input2 {width: 100%;}
      ⍝ #List {display: inline-block; margin: 1em; padding: 5px 5px 5px 25px;}
      ⍝ #Table {display: inline-block; max-height: 7em; overflow-y: scroll; margin-top: 1em;}
      ⍝
      ⍝ /**/#span:hover, #p:hover, #div:hover, #Button:hover, #StackPanel:hover, #EditField:hover, #Select:hover, #Fieldset1:hover, #Fieldset2:hover, .CheckBox:hover, #RadioButtonGroup:hover, #Input1:hover, #Input2:hover, #h5:hover, #progress:hover, #List:hover, #Table:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      'ShowElement'∘AddHandler¨'#div' '#p' '#span' '#Button' '#EditField' '#StackPanel' '#Select' '#Fieldset1' '#Fieldset2' '.CheckBox' '#RadioButtonGroup' '#Input1' '#h5' '#Input2' '#progress' '#List' '#Table'
      t←'Common elements't
    ∇

    ∇ t←Formatting
      t←'#margrad'New _.div'This div has inner padding and gradient backround' ⍝ #margrad
      '#boldin't.Add _.p'Bold indented text which has to be extremely long in order to show that only the first line is being indented.' ⍝ #boldin
      '#huge't.Add _.span'Huge text with outer margin' ⍝ #huge
      '#expund't.Add _.span'Expanded and underlined' ⍝ #expund
      '#bluebox't.Add _.p'Blue box with shadow' ⍝ #bluebox
      '#centshad't.Add _.p'Centered text with shadow' ⍝ #centshad
      '#whimg't.Add _.p'White text on image background' ⍝ #whimg
      '#border't.Add _.p'Surrounded by borders and top-right corner is round' ⍝ #border
      '#img' 'src="Examples/Data/4lines.png"' 'alt="Image"' 'title="Four lines high"'t.Add _.img ⍝ #img
      '#narrow't.Add _.p'Inline paragraph with limited width and single line height.' ⍝ #narrow
      '#mono't.Add _.span'Monospace, ' ⍝ #mono
      '#sans't.Add _.span'Sans-Serif, ' ⍝ #sans
      '#serif't.Add _.span'and Serif.' ⍝ #serif
      t.Add _.style ScriptFollows
      ⍝ #margrad {padding: 2em; background: linear-gradient(White, SkyBlue);}
      ⍝ #boldin {font-weight: bold; text-indent: 2em;}
      ⍝ #huge {font-size: x-large; margin: 2em;}
      ⍝ #expund {text-decoration: underline; letter-spacing: 0.5ex;}
      ⍝ #bluebox {background-color: SkyBlue; box-shadow: 0.25em 0.5em 1em Gray;}
      ⍝ #centshad {text-align: center; text-shadow: 0.1em 0.3em 0.2em Blue;}
      ⍝ #whimg {color: white; background-image: url(Examples/Data/pic3.png);}
      ⍝ #serif {font-family: "Times New Roman", Times, serif; font-size: 150%;}
      ⍝ #sans {font-family: Arial, Helvetica, sans-serif; font-size: 150%;}
      ⍝ #mono {font-family: "Courier New", Courier, monospace; font-size: 150%;}
      ⍝ #border {border: 0.25em solid Blue; border-top-right-radius: 1.5em;}
      ⍝ #img {height: 4em; opacity: 0.5;}
      ⍝ #narrow {width: 20%; display: inline-block; line-height: 1;}
      ⍝
      ⍝ /**/#boldin:hover, #huge:hover, #expund:hover, #centshad:hover, #serif:hover, #sans:hover, #mono:hover, #narrow:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      ⍝ /**/#margrad:hover {box-shadow: 0 0 5px 2px Orange inset;}
      ⍝ /**/#whimg:hover {box-shadow: 0 0 5px 2px Orange;}
      ⍝ /**/#border:hover {box-shadow: 0 0 5px 2px Orange, 0 0 5px 2px Orange inset;}
      ⍝ /**/#img:hover {box-shadow: 0 0 5px 3px Orange,0 0 5px 3px Orange;}
      ⍝ /**/#bluebox:hover {box-shadow:0 0 5px 1px Orange, 0 0 5px 1px Orange inset, 0.25em 0.5em 1em Gray;}
      'ShowFormatting'∘AddHandler¨'#margrad' '#boldin' '#huge' '#expund' '#bluebox' '#centshad' '#whimg' '#serif' '#sans' '#mono' '#border' '#img' '#narrow'
      t←'Formatting't
    ∇

    ∇ t←Positioning
      Add _.style ScriptFollows
      ⍝ #parent {position: relative; height: 26em; margin: 1em;}
      ⍝ /**/#parent, #parent div {margin-top: 1em; margin-bottom: 1em; padding: 1ex;}
      ⍝ /**/#parent div {width: 50%; border: 1px solid black; }
      ⍝ #default {/* nothing to see here */}
      ⍝ #textcenter {text-align: center;}
      ⍝ #textright {text-align: right;}
      ⍝ #boxright {margin-left: auto; margin-right: 0;}
      ⍝ #boxcenter {margin-left: auto; margin-right: auto;}
      ⍝ #bottom {position: absolute; bottom: 0;}
      ⍝
      ⍝ /**/#parent:hover, #parent div:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      t←'#parent'New _.div'Parent element with "relative" position so we can align vertically' ⍝ #parent
      '#default't.Add _.div'Default position and text alignment' ⍝ #default
      '#textcenter't.Add _.div'Centered text' ⍝ #textcenter
      '#textright't.Add _.div'Right-aligned text' ⍝ #textright
      '#boxcenter't.Add _.div'Centered element' ⍝ #boxcenter
      '#boxright't.Add _.div'Right-aligned element' ⍝ #boxright
      '#bottom't.Add _.div'Bottom-aligned element'
      'ShowPositioning'∘AddHandler¨'#parent' '#default' '#textcenter' '#textright' '#boxcenter' '#boxright' '#middle' '#bottom'
      t←'Positioning't
    ∇

    ∇ t←Embedding
      Add _.style ScriptFollows
      ⍝ #embed * {margin: 1ex;}
      ⍝ #quack {filter: hue-rotate(-205deg); padding: 2em;}
      ⍝ #quack:hover {filter:  hue-rotate(0deg);}
      ⍝ #bear {display: block; padding: 0.2em; border: 1px solid silver; margin: 1ex;}
      ⍝ #duck {height: 12em; transform: rotate(-15deg);}
      ⍝
      ⍝ /**/#embed *:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      t←'#embed'New _.div'This div contains embedded items'
      t.Add _.br
      '#quack' 'controls='t.Add _.Audio'Examples/Data/quack.mp3' ⍝ #quack
      '#duck' 'src=Examples/Data/duck.png' 'alt="Silly Duck"'t.Add _.img'No img support!' ⍝ #duck
      '#bear' 'src=Examples/Data/bear.mp4' 'autoplay=' 'loop=' 'muted='t.Add _.video'No video support!' ⍝ #bear
      'ShowEmbedding'∘AddHandler¨'#bear' '#duck' '#quack'
      t←'Embedding't
    ∇

    ∇ t←Interaction
      t←'#ui'New _.div'This div shows user interaction'
      t←'Interaction't
    ∇

    ∇ t←Windows
      t←'#win'New _.div'This div shows floating GUI elements'
      t←'Windows & Popups't
    ∇

    ∇ {ctrl}←callbackfn AddHandler sel
      ctrl←sel~'#.12'
      Add _.Handler sel'click'callbackfn'' 'event.stopPropagation()'
    ∇

    ∇ r←ShowElement;code;ctrl;info
      :Access Public
      ctrl←_selector~'#12'
      info←'
This element is called _.',ctrl,'
'
      info,←P'descr'ForControl ctrl
      info,←'
Constructor (what goes to the right of Add _.',ctrl,')
'
      info,←P Link('ctor'ForControl ctrl)(DocUrl ctrl)
      code←⊃_selector CodeFrom'Elements'
      info,←'
Put this in the Compose function
',P code
      r←'#info'Replace info
    ∇

    ∇ r←ShowHtmlAndCss fn;css;info;code
      (code css)←_selector CodeFrom fn
      info←'
Put this in the Compose function
',P code
      info,←'
Put Add _.style''…'' in Compose or add  this to the stylesheet
',P css
      r←'#info'Replace info
    ∇

    ∇ r←ShowFormatting
      :Access Public
      r←ShowHtmlAndCss'Formatting'
    ∇

    ∇ r←ShowPositioning
      :Access Public
      r←ShowHtmlAndCss'Positioning'
    ∇

    ∇ r←ShowEmbedding
      :Access Public
      r←ShowHtmlAndCss'Embedding'
    ∇

      CodeFrom←{
          nr←⎕NR ⍵
          (css lines)←↓⍉↑('⍝ ',⍺)∘Prep¨nr/⍨('⍝ ',⍺)∘(∨/⍷)¨nr
          (lines/⍨~css)(css/lines)
      }

      Prep←{
          c←#.Strings.dlb ⍵
          ⍺≡c↑⍨-l←≢⍺:0('\w+\.Add'⎕R'Add'⊢c↓⍨¯1-l)
          1(2↓c)
      }

    P←{∊('
',,∘'
')¨ç⍵}

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION F_CONSTANTS ⍝ NILADIC FUNCTIONS THAT ACT LIKE :include'ABLE CONSTANTS

    ∇ r←NSS
      :Access public
      r←'DC' 'SF' 'JQ' 'JS' 'html'
    ∇

    ∇ r←GROUPS
      :Access public
      r←'Dyalog' 'Syncfusion' 'jQueryUI' 'JavaScript' 'Base HTML'
    ∇

    ∇ r←CACHE
      :Access public
      r←'#.CACHE'
    ∇

    ∇ r←FILEEXT
      :Access public
      r←#.Boot.ms.Config.DefaultExtension
    ∇

    ∇ C←C;scores;list;refs;srcs;control;i;info;ref;src;ctor;Read;Ø;demo
     ⍝ Return ref to cache (initialize cache if nonexistant)
      :Access public
      Read←#.Files.GetVTV #.Boot.AppRoot∘, ⍝ no-cache reading
      :Hold CACHE                          ⍝ prevent clashes
          :If 9≠⎕NC CACHE                  ⍝ if cache is empty:
              Ø←⊂''
              C←⍎CACHE ⎕NS ⍬       ⍝ create with shortcut
              C.read←⎕NS ⍬         ⍝ init cache for files
              C.read.(keys←data←⍬) ⍝ init keys and data

              C.controls←(#._.⎕NL ¯9)~⊂'Handler'                       ⍝ cache all controls
              C.controlsoi←C.controls∘⍳ç ⋄ C.eocontrols←∊∘C.controls ç ⍝ hash tables
              refs←#._⍎¨C.controls                                     ⍝ refs of all controls
              C.ns←3↓¨⍕¨refs.##                                        ⍝ ns of each control
              srcs←⎕SRC¨refs                                           ⍝ all sources

              C.files←⊃⍪/{List'Examples/',⍵}¨NSS,⊂'Applications' ⍝ sample filenames
              C.filesoi←C.files∘⍳ç                               ⍝ hash table
              C.filedescr←('Description'Section Read)¨C.files    ⍝ Description:: for all files
              C.demos←Controls¨C.files                           ⍝ controls demoed in each
              scores←C.controls∘.Score↓⍉↑C.files C.demos         ⍝ controls vs files
              C.rankings←(+/0<scores)↑¨↓⍒#.Utils.∆rank 1⊢scores  ⍝ cache all rankings

              C.(descr ctor notes relevant reldocs doclinks)←Ø
              info←FromCSV Read'IndexData/info.csv'
              :For control ref src :InEach C.controls refs srcs
                  i←info[;1]⍳⊂control
                  :If i≤≢info
                      C.descr,←info[i;2]
                      C.notes,←info[i;3]
                  :Else
                      C.descr,←⊂NoNL'Description'Section src
                      C.notes,←⊂NoNL'Notes'Section src
                  :EndIf

                  ctor←{⍵↑⍨¯1+⌊/⍵⍳⎕UCS 13 10}'Constructor'Section src
                  :If ''≡ctor
                      C.ctor,←'[content [attributes]]' '[attributes]'[1+(⎕NEW ref).NoEndTag]
                  :Else
                      C.ctor,←⊂ctor
                  :EndIf

                  :If C.eocontrols⊂control
                      C.relevant,←⊂C.files[C.rankings⊃⍨C.controlsoi⊂control]
                  :Else
                      C.relevant,←Ø
                  :EndIf

              :EndFor
              C.cats←GROUPS[NSS⍳3↓¨⍕¨('#._'∘⍎¨C.controls).##]

              C.reldocs←(RelDocs¨C.controls),Ø
              C.doclink←(DocLink¨C.controls),Ø
              C.used←{⊂2↓∊', '∘,¨C.doclink[C.controlsoi¨⍵]}¨C.demos

              C.tasks←{Link('Description'Section Read ⍵)⍵}¨C.files

              C.type←'simple' 'advanced' 'mini-app'[1⍳⍨¨↓⍉∨/¨'Simple' 'Advanced'∘.⍷C.files]

              ⍝ These cause an empty result if looked-up is not found
              C.(filedescr demos rankings descr ctor notes notes ns),←Ø

          :Else
              C←⍎CACHE ⍝ establish shortcut
          :EndIf
      :EndHold
    ∇

      ForControl←{ ⍝ e.g. 'notes'ForControl'DataTable'
          (C.controlsoi⊂,⍵)⊃C⍎⍺
      }
      ForFile←{ ⍝ e.g. 'filedescr'ForFile'DataTable'
          (C.filesoi⊂,⍵)⊃C⍎⍺
      }

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

:EndNamespace

:Namespace MS3SiteUtils ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝
⍝ ┌─────────────────────────────────────────────────────────────────────────────────────────────┐ ⍝
⍝ │ This is the collection of utility functions used by MS3Server and the index page of the     │ ⍝
⍝ │ MiServer Sample Site, MS3, accessible at miserver.dyalog.com                                │ ⍝
⍝ ├─────────────────────────────────────────────────────────────────────────────────────────────┤ ⍝
⍝ │ The code is split up in six sections:                                                       │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ A_GENERAL has utilities that appropriate to APL or MiServer in general                │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ B_FILES has various tools to list, read, and cache files and their content            │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ C_STRINGS string and multi-string manipulation and extraction of information          │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ D_ELEMENTS aids to create elements for the HTML Document Object Model                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ E_TUTORIAL has subroutines for the Tutorial tab on the index page                     │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ F_CONSTANTS niladic functions that return unchanging values and cache                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ └─────────────────────────────────────────────────────────────────────────────────────────────┘ ⍝
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION A_GENERAL ⍝ UTILITY FUNCTIONS THAT ARE NOT SPECIFIC TO THE "MS3" SITE

    ç←{⊂⍣(1=≡,⍵)⊢⍵} ⍝ enclose if simple     Mnemonic for the V16 glyph

    FwSl←'[\\/]+'⎕R'/' ⍝ Make all slash-block into single forward-slashes

    Q←'"'∘,,∘'"' ⍝ Surround with quotes

    FromCSV←↑{'"'~¨⍨1↓¨t⊂⍨(','=t)∧~≠\'"'=t←',',⍵}¨ ⍝ Convert CSV VTV to matrix (no escaped chars)

      Section←{ ⍝ Extract section ⍺:: from code ⍵
          regex←'^\s*⍝\s*',⍺,':(:.*?)$((\R^⍝(?!\s*\w+::).*?$)*)' ⍝ find '  ⍝  LeftArg:: some text'
          opts←('Mode' 'M')('DotAll' 1)('ML' 1)                  ⍝ '^'≡linestart  EOL∊'.'  1st-only
          res←regex ⎕S'\1\2'⍠opts⊢⍵                              ⍝ return parts 1 and 2
          #.Strings.deb'⍝'~⍨1↓⊃res                               ⍝ strip spaces and lamp
      }

      Controls←{ ⍝ Extract names of controls used in sample ⍵
          (Words'Control'Section Read ⍵)~'_',¨NSS
      }

    Of←{⍵/⍨⍺⍺¨⍵} ⍝ Those elements of ⍵ that satisfy ⍺⍺ element

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION B_FILES ⍝ FUNCTIONS THAT READ FILES

    Score←{(102-2×(50↑⊃⌽⍵)⍳⊂⍺)-∨/'Advanced'⍷⊃⍵} ⍝ Relevance score on index of control in list

      List←{ ⍝ Files in site folder
          ⍺←1↓FILEEXT                                      ⍝ default .ext
          mods←' -rec -raw -full=2 -ext=',⍺                ⍝ subdirs no-format rooted spec-ext
          list←(⎕SE.SALT.List #.Boot.AppRoot,⍵,mods)[;1 2] ⍝ begin at application root (MS3/)
          f←~d←list[;1]≡¨⊂'<DIR>'                          ⍝ find dirs
          list←FwSl list[;2]                               ⍝ normalize slashes for WWW
          (d/list),←'/'                                    ⍝ mark dirs with final slashes
          (f/list),←⊂'.',⍺                                 ⍝ add .ext to files
          list↓¨⍨¯1+1⍳⍨(FwSl ⍵)⍷⊃list                      ⍝ trim
      }

    ∇ r←Read page;i ⍝ Read a page via the #.CACHE.read (C)
      :Trap 0
          :If (≢C.read.keys)≥i←C.read.keys⍳⊂page ⍝ is it cached, and if so, save position
              r←i⊃C.read.data                     ⍝ extract
          :Else
              C.read.data,←⊂r←#.Files.GetVTV #.Boot.AppRoot,page,(~'.'∊page)/FILEEXT
              C.read.keys,←⊂page
          :EndIf
      :Else
          r←,⊂'[failed to read "',page,'"]'
      :EndTrap
    ∇

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION C_STRINGS ⍝ MANIPULATION OF CHARACTER VECTORS AND VECTORS OF CHARACTER VECTORS

    Slash←'/'∘=⊂⊢

    Name←{1↓⊃⌽(Slash ⍵)~⊂,'/'}  ⍝ '/aaa/bbb/ccc/ddd/' →         'ddd' (only in BuildTree)

    Words←{(1↓¨(∊∘' .'⊂⊢)' ',⍵)~⊂''} ⍝ Split at spaces and dots and remove empty pieces

    NoExt←{'.'∊⍵:⍵↓⍨-'.'⍳⍨⌽⍵⋄⍵} ⍝ 'aaa.bbb.ccc'  →  'aaa.bbb'

    NoNL←'\n' '\r'⎕R' ' ''⍠'Mode' 'D' ⍝ Replace newlines with spaces

    LastSeg←{⍺←⊢ ⋄ 1↓⍺⊃⌽Slash '/',⍵} ⍝ ⍺th (default 1st) /-separated segment from the end

    Levels←{(+/¨'/'=⍵)+~'/'=(⊃⌽)¨⍵} ⍝ Number of /s adjusted for categories' final /

    Shuffle←(?⍨∘≢⊃¨⊂)⊢⊂⍨⊢≡¨1↑⊢ ⍝ Shuffle the root elements of an XML snippet

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION D_ELEMENTS ⍝ FUNCTIONS THAT AUTOMATE THE CREATION OF DOM ELEMENTS AND HTML CODE

      Over←{
          ⊂'',⍺,'
',('/'⎕R'
'⊢⍵),''
      }

      External←{ ⍝ Icon off-site link
          ⍺←''
      ⍝    '.external' 'target=_blank' 'data-dyalog-tip="External link"'New _.A((⍺,'🔗')⍵)
          '',⍺,'🔗'
      }

      DescrEmbed←{ ⍝ Link to and iframed page
          l←('Description'Section Read ⍵),'
'
          l,←'Open this '
          l,←'simple sample' 'advanced sample' 'mini-app'⊃⍨1⍳⍨∨/¨'Simple' 'Advanced'⍷¨⊂⍵
          l,←' in a new tab'
          e←''
          l e
      }

      Link←{ ⍝ New-tab link with optional (⍺) "tooltip"
          ⍺←0
          ⍝(('data-dyalog-tip=',Q ⍺){⍺ ⍵}⍣(⍺≢0)⊢'target="_blank"')New _.A ⍵
          '',(⊃⍵),''
      }

      DocLink←{ ⍝ Link to WidgetDoc with appropriate parameters
          link←DocUrl ⍵
          ctor←'ctor'ForControl ⍵
          0=⍴ctor::Link ⍵ link ⍝ upon fail to retrieve info
          ('Constructor: ',ctor)Link ⍵ link
      }

      DocUrl←{
          '/Documentation/DyalogAPIs/WidgetDoc?namespace=_',('ns'ForControl ⍵),'&widget=',⍵
      }

      DocTreeLink←{ ⍝ Link to file in doc folder
          '/'=⊃⌽⍵:''
          ('/Documentation',⍵)'target="_blank"'
      }

      ReadingTree←{ ⍝ Create tree of documentation links
          l←'/',¨⍵~⊂''
          (Name∘NoExt¨l)(Levels l)(DocTreeLink¨l)
      }

    ∇ r←RelDocs w;list;item ⍝ Links to related samples
      list←'relevant'ForControl w
      r←''
      :For item :In list
          r,←'
',(Link('filedescr'ForFile item)item),'

'
      :EndFor
      r↓⍨←¯5
    ∇

      FormatList←{ ⍝ List of category-title pairs
          ⍺←1↓FILEEXT
          (⍺≡1↓FILEEXT)CatAndItem¨{'/'≠⊃⌽⍵}Of ⍺ List ⍵
      }

      CatAndItem←{ ⍝ (category) (filename/description)
          cat←2 LastSeg ⍵
          cat←'General' 'Mini App'cat['Documentation' 'Applications'⍳⊂cat]
          cat(Link((NoExt LastSeg)⍣(~⍺)('Description'Section Read)⍣⍺⊢⍵)⍵)
      }

      FilterTable←{
          dt←⍺ New _.DataTable ⍵
          dt.Options.mark←_true                ⍝ highlight search-terms
          dt.Options.columns←⎕NS¨4⍴⊂⍬          ⍝ options per column
          dt.Options.columns[4].visible←_false ⍝ hide column4
          dt.Options.dom←'l<"#fltcnt">ftpr'    ⍝ insert div with id "fltcnt" into table header
          dt.Plugins←'yadcf'
          dt.Options.yadcf←⎕NS ⍬
          dt.Options.yadcf.Filters←⎕NS ⍬
          dt.Options.yadcf.Filters.(column_number select_type filter_container_id)←3 'chosen' 'fltcnt'
          dt.Options.yadcf.Filters.(filter_type select_type_options filter_default_label)←'multi_select'(⊂'{width:"30em"}')'Filter by library'
          dt.Options.yadcf.Filters/⍨←1 ⍝ needs array of namespaces
          dt
      }

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION E_TUTORIAL ⍝ FUNCTIONS USED BY THE index PAGE'S Tutorial TAB

    ∇ t←Elements;tabs;r;sp;fs;ig;colors;rgbs;button;p;cbs;t2;g
      t←'#div'New _.div'This div is the main container of this tab. ' ⍝ #div
      p←'#p't.Add _.p'This is the beginning of a paragraph, ' ⍝ #p
      '#span'p.Add _.span'but this phrase is a separate piece of text inside the paragraph. ' ⍝ #span
      button←'#Button't.Add _.Button'Button' ⍝ #Button
      sp←'#StackPanel't.Add _.StackPanel ⍝ #StackPanel
      sp.Add'StackPanel'
      '#EditField'sp.Add _.EditField'EditField' 'EditField' ⍝ #EditField
      '#Select'sp.Add _.Select'Red' 'Green' 'Blue' ⍝ #Select
      '#Input1't.Add _.Input'number' 426 ' Number Input: ' ⍝ #Input1
      t.Add _.br
      fs←'#Fieldset1't.Add _.Fieldset'CheckBoxes' ⍝ #Fieldset1
      cbs←{'.CheckBox'New _.CheckBox ⍵}¨'checked' 'unchecked' ⍝ .CheckBox
      ig←'#InputGrid'fs.Add _.InputGrid('Checked' 'Unchecked')cbs ⍝ #InputGrid
      ig.Flip←1 ⍝ #InputGrid
      fs←'#Fieldset2't.Add _.Fieldset'RadioButtonGroup' ⍝ #Fieldset2
      '#RadioButtonGroup'fs.Add _.RadioButtonGroup('Selected' 'Unselected')1 'right' ⍝ #RadioButtonGroup
      '#h5't.Add _.h5'Slider' ⍝ #h52
      '#Input2't.Add _.Input'range' 1 ⍝ #Input2
      '#List't.Add _.List('Primaries' 'Cyan' 'Magenta' 'Yellow' 'Key' 'Black')(1 2 2 2 1 2) ⍝ #List
      colors←'aqua' 'black' 'blue' 'fuchsia' 'gray' 'green' 'lime' 'maroon' ⍝ #Table
      rgbs←'#00FFFF' '#000000' '#0000FF' '#FF00FF' '#808080' '#008000' '#00FF00' '#800000' ⍝ #Table
      colors,←'navy' 'olive' 'purple' 'red' 'silver' 'teal' 'white' 'yellow' ⍝ #Table
      rgbs,←'#000080' '#808000' '#800080' '#FF0000' '#C0C0C0' '#008080' '#FFFFFF' '#FFFF00' ⍝ #Table
      '#Table't.Add _.Table('Color' 'RGB'⍪colors,⍪rgbs)'' 1 ⍝ #Table
      t.Add _.style ScriptFollows
      ⍝ #div {padding: 10px; margin: 10px;}
      ⍝ #Button {vertical-align: top; margin: 5px;}
      ⍝ #StackPanel {display: inline-block; padding: 5px;}
      ⍝ #Fieldset1 {display: inline-block;}
      ⍝ #Fieldset2 {display: inline-block;}
      ⍝ #h5 {margin: 1em 0 0.5em 0;}
      ⍝ #Input1 {width: 4em;}
      ⍝ #Input2 {width: 100%;}
      ⍝ #List {display: inline-block; margin: 1em; padding: 5px 5px 5px 25px;}
      ⍝ #Table {display: inline-block; max-height: 7em; overflow-y: scroll; margin-top: 1em;}
      ⍝
      ⍝ /**/#span:hover, #p:hover, #div:hover, #Button:hover, #StackPanel:hover, #EditField:hover, #Select:hover, #Fieldset1:hover, #Fieldset2:hover, .CheckBox:hover, #RadioButtonGroup:hover, #Input1:hover, #Input2:hover, #h5:hover, #progress:hover, #List:hover, #Table:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      'ShowElement'∘AddHandler¨'#div' '#p' '#span' '#Button' '#EditField' '#StackPanel' '#Select' '#Fieldset1' '#Fieldset2' '.CheckBox' '#RadioButtonGroup' '#Input1' '#h5' '#Input2' '#progress' '#List' '#Table'
      t←'Common elements't
    ∇

    ∇ t←Formatting
      t←'#margrad'New _.div'This div has inner padding and gradient backround' ⍝ #margrad
      '#boldin't.Add _.p'Bold indented text which has to be extremely long in order to show that only the first line is being indented.' ⍝ #boldin
      '#huge't.Add _.span'Huge text with outer margin' ⍝ #huge
      '#expund't.Add _.span'Expanded and underlined' ⍝ #expund
      '#bluebox't.Add _.p'Blue box with shadow' ⍝ #bluebox
      '#centshad't.Add _.p'Centered text with shadow' ⍝ #centshad
      '#whimg't.Add _.p'White text on image background' ⍝ #whimg
      '#border't.Add _.p'Surrounded by borders and top-right corner is round' ⍝ #border
      '#img' 'src="Examples/Data/4lines.png"' 'alt="Image"' 'title="Four lines high"'t.Add _.img ⍝ #img
      '#narrow't.Add _.p'Inline paragraph with limited width and single line height.' ⍝ #narrow
      '#mono't.Add _.span'Monospace, ' ⍝ #mono
      '#sans't.Add _.span'Sans-Serif, ' ⍝ #sans
      '#serif't.Add _.span'and Serif.' ⍝ #serif
      t.Add _.style ScriptFollows
      ⍝ #margrad {padding: 2em; background: linear-gradient(White, SkyBlue);}
      ⍝ #boldin {font-weight: bold; text-indent: 2em;}
      ⍝ #huge {font-size: x-large; margin: 2em;}
      ⍝ #expund {text-decoration: underline; letter-spacing: 0.5ex;}
      ⍝ #bluebox {background-color: SkyBlue; box-shadow: 0.25em 0.5em 1em Gray;}
      ⍝ #centshad {text-align: center; text-shadow: 0.1em 0.3em 0.2em Blue;}
      ⍝ #whimg {color: white; background-image: url(Examples/Data/pic3.png);}
      ⍝ #serif {font-family: "Times New Roman", Times, serif; font-size: 150%;}
      ⍝ #sans {font-family: Arial, Helvetica, sans-serif; font-size: 150%;}
      ⍝ #mono {font-family: "Courier New", Courier, monospace; font-size: 150%;}
      ⍝ #border {border: 0.25em solid Blue; border-top-right-radius: 1.5em;}
      ⍝ #img {height: 4em; opacity: 0.5;}
      ⍝ #narrow {width: 20%; display: inline-block; line-height: 1;}
      ⍝
      ⍝ /**/#boldin:hover, #huge:hover, #expund:hover, #centshad:hover, #serif:hover, #sans:hover, #mono:hover, #narrow:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      ⍝ /**/#margrad:hover {box-shadow: 0 0 5px 2px Orange inset;}
      ⍝ /**/#whimg:hover {box-shadow: 0 0 5px 2px Orange;}
      ⍝ /**/#border:hover {box-shadow: 0 0 5px 2px Orange, 0 0 5px 2px Orange inset;}
      ⍝ /**/#img:hover {box-shadow: 0 0 5px 3px Orange,0 0 5px 3px Orange;}
      ⍝ /**/#bluebox:hover {box-shadow:0 0 5px 1px Orange, 0 0 5px 1px Orange inset, 0.25em 0.5em 1em Gray;}
      'ShowFormatting'∘AddHandler¨'#margrad' '#boldin' '#huge' '#expund' '#bluebox' '#centshad' '#whimg' '#serif' '#sans' '#mono' '#border' '#img' '#narrow'
      t←'Formatting't
    ∇

    ∇ t←Positioning
      Add _.style ScriptFollows
      ⍝ #parent {position: relative; height: 26em; margin: 1em;}
      ⍝ /**/#parent, #parent div {border: 1px solid black; margin-top: 1em; margin-bottom: 1em; padding: 1ex;}
      ⍝ /**/#parent div {width: 50%;}
      ⍝ #default {/* nothing to see here */}
      ⍝ #textcenter {text-align: center;}
      ⍝ #textright {text-align: right;}
      ⍝ #boxright {margin-left: auto; margin-right: 0;}
      ⍝ #boxcenter {margin-left: auto; margin-right: auto;}
      ⍝ #bottom {position: absolute; bottom: 0;}
      ⍝
      ⍝ /**/#parent:hover, #parent div:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      t←'#parent'New _.div'Parent element with "relative" position so we can align vertically' ⍝ #parent
      '#default't.Add _.div'Default position and text alignment' ⍝ #default
      '#textcenter't.Add _.div'Centered text' ⍝ #textcenter
      '#textright't.Add _.div'Right-aligned text' ⍝ #textright
      '#boxcenter't.Add _.div'Centered element' ⍝ #boxcenter
      '#boxright't.Add _.div'Right-aligned element' ⍝ #boxright
      '#bottom't.Add _.div'Bottom-aligned element'
      'ShowPositioning'∘AddHandler¨'#parent' '#default' '#textcenter' '#textright' '#boxcenter' '#boxright' '#middle' '#bottom'
      t←'Positioning't
    ∇

    ∇ t←Embedding
      t←'#embed'New _.div'This div contains embedded items'
      t←'Embedding't
    ∇

    ∇ t←Interaction
      t←'#ui'New _.div'This div shows user interaction'
      t←'Interaction't
    ∇

    ∇ t←Windows
      t←'#win'New _.div'This div shows floating GUI elements'
      t←'Windows & Popups't
    ∇

    ∇ {ctrl}←callbackfn AddHandler sel
      ctrl←sel~'#.12'
      Add _.Handler sel'click'callbackfn'' 'event.stopPropagation()'
    ∇

    ∇ r←ShowElement;code;ctrl;info
      :Access Public
      ctrl←_selector~'#12'
      info←'
This element is called _.',ctrl,'
'
      info,←P'descr'ForControl ctrl
      info,←'
Constructor (what goes to the right of Add _.',ctrl,')
'
      info,←P Link('ctor'ForControl ctrl)(DocUrl ctrl)
      code←⊃_selector CodeFrom'Elements'
      info,←'
Put this in the Compose function
',P code
      r←'#info'Replace info
    ∇

    ∇ r←ShowFormatting;css;info;code
      :Access Public
      (code css)←_selector CodeFrom'Formatting'
      info←'
Put this in the Compose function
',P code
      info,←'
Put Add _.style''…'' in Compose or add  this to the stylesheet
',P css
      r←'#info'Replace info
    ∇

    ∇ r←ShowPositioning;css;info;code
      :Access Public
      (code css)←_selector CodeFrom'Positioning'
      info←'
Put this in the Compose function
',P code
      info,←'
Put Add _.style''…'' in Compose or add  this to the stylesheet
',P css
      r←'#info'Replace info
    ∇

      CodeFrom←{
          nr←⎕NR ⍵
          (css lines)←↓⍉↑('⍝ ',⍺)∘Prep¨nr/⍨('⍝ ',⍺)∘(∨/⍷)¨nr
          (lines/⍨~css)(css/lines)
      }

      Prep←{
          c←#.Strings.dlb ⍵
          ⍺≡c↑⍨-l←≢⍺:0('\w+\.Add'⎕R'Add'⊢c↓⍨¯1-l)
          1(2↓c)
      }

    P←{∊('
',,∘'
')¨ç⍵}

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION F_CONSTANTS ⍝ NILADIC FUNCTIONS THAT ACT LIKE :include'ABLE CONSTANTS

    ∇ r←NSS
      :Access public
      r←'DC' 'SF' 'JQ' 'JS' 'html'
    ∇

    ∇ r←GROUPS
      :Access public
      r←'Dyalog' 'Syncfusion' 'jQueryUI' 'JavaScript' 'Base HTML'
    ∇

    ∇ r←CACHE
      :Access public
      r←'#.CACHE'
    ∇

    ∇ r←FILEEXT
      :Access public
      r←#.Boot.ms.Config.DefaultExtension
    ∇

    ∇ C←C;scores;list;refs;srcs;control;i;info;ref;src;ctor;Read;Ø;demo
     ⍝ Return ref to cache (initialize cache if nonexistant)
      :Access public
      Read←#.Files.GetVTV #.Boot.AppRoot∘, ⍝ no-cache reading
      :Hold CACHE                          ⍝ prevent clashes
          :If 9≠⎕NC CACHE                  ⍝ if cache is empty:
              Ø←⊂''
              C←⍎CACHE ⎕NS ⍬       ⍝ create with shortcut
              C.read←⎕NS ⍬         ⍝ init cache for files
              C.read.(keys←data←⍬) ⍝ init keys and data

              C.controls←(#._.⎕NL ¯9)~⊂'Handler'                       ⍝ cache all controls
              C.controlsoi←C.controls∘⍳ç ⋄ C.eocontrols←∊∘C.controls ç ⍝ hash tables
              refs←#._⍎¨C.controls                                     ⍝ refs of all controls
              C.ns←3↓¨⍕¨refs.##                                        ⍝ ns of each control
              srcs←⎕SRC¨refs                                           ⍝ all sources

              C.files←⊃⍪/{List'Examples/',⍵}¨NSS,⊂'Applications' ⍝ sample filenames
              C.filesoi←C.files∘⍳ç                               ⍝ hash table
              C.filedescr←('Description'Section Read)¨C.files    ⍝ Description:: for all files
              C.demos←Controls¨C.files                           ⍝ controls demoed in each
              scores←C.controls∘.Score↓⍉↑C.files C.demos         ⍝ controls vs files
              C.rankings←(+/0<scores)↑¨↓⍒#.Utils.∆rank 1⊢scores  ⍝ cache all rankings

              C.(descr ctor notes relevant reldocs doclinks)←Ø
              info←FromCSV Read'IndexData/info.csv'
              :For control ref src :InEach C.controls refs srcs
                  i←info[;1]⍳⊂control
                  :If i≤≢info
                      C.descr,←info[i;2]
                      C.notes,←info[i;3]
                  :Else
                      C.descr,←⊂NoNL'Description'Section src
                      C.notes,←⊂NoNL'Notes'Section src
                  :EndIf

                  ctor←{⍵↑⍨¯1+⌊/⍵⍳⎕UCS 13 10}'Constructor'Section src
                  :If ''≡ctor
                      C.ctor,←'[content [attributes]]' '[attributes]'[1+(⎕NEW ref).NoEndTag]
                  :Else
                      C.ctor,←⊂ctor
                  :EndIf

                  :If C.eocontrols⊂control
                      C.relevant,←⊂C.files[C.rankings⊃⍨C.controlsoi⊂control]
                  :Else
                      C.relevant,←Ø
                  :EndIf

              :EndFor
              C.cats←GROUPS[NSS⍳3↓¨⍕¨('#._'∘⍎¨C.controls).##]

              C.reldocs←(RelDocs¨C.controls),Ø
              C.doclink←(DocLink¨C.controls),Ø
              C.used←{⊂2↓∊', '∘,¨C.doclink[C.controlsoi¨⍵]}¨C.demos

              C.tasks←{Link('Description'Section Read ⍵)⍵}¨C.files

              C.type←'simple' 'advanced' 'mini-app'[1⍳⍨¨↓⍉∨/¨'Simple' 'Advanced'∘.⍷C.files]

              ⍝ These cause an empty result if looked-up is not found
              C.(filedescr demos rankings descr ctor notes notes ns),←Ø

          :Else
              C←⍎CACHE ⍝ establish shortcut
          :EndIf
      :EndHold
    ∇

      ForControl←{ ⍝ e.g. 'notes'ForControl'DataTable'
          (C.controlsoi⊂,⍵)⊃C⍎⍺
      }
      ForFile←{ ⍝ e.g. 'filedescr'ForFile'DataTable'
          (C.filesoi⊂,⍵)⊃C⍎⍺
      }

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

:EndNamespace


Delete MiPageSample.dyalog.BACKUP.15104.dyalog

:Class MiPageSample : #.EAWC
⍝ This is a template that "wraps" the page content by
⍝ - adding a header and footer
⍝ - adding a handler that will toggle the display of the web page and its APL source code

    ∇ Wrap;lang;server;controls
      :Access Public

      server←_Request.Server

      controls←∪Walk Body.Content

    ⍝ we use Syncfusion (which uses jQuery) to set up the controls to do cool stuff
      Use'Syncfusion' ⍝ this is a resource defined in Config/Resources.xml

    ⍝ set the title display in the browser to the name of the application defined in Config/Server.xml
      Add title server.Config.Name

    ⍝ add a link to our CSS stylesheet
      (Add link).SetAttr(('href' '/Styles/style.css')('rel' 'stylesheet')('type' 'text/css'))

    ⍝ set a meta tag to make it explicitly UTF-8
      (Add meta).SetAttr'http-equiv="content-type" content="text/html;charset=UTF-8"'

    ⍝ wrap the content of the <body> element in a div
      Body.Push div'id="contentblock"'

    ⍝ add a hidden division to the body containing the APL source code
      (Add div(#.HTMLInput.APLToHTML ⎕SRC⊃⊃⎕CLASS ⎕THIS)).SetAttr'id="codeblock"' 'style="display: none;"'

    ⍝ add a JQuery event handler to toggle the web page/APL source code
      Add _HTML.Script'$(function(){$("#bannerimage").on("click", function(evt){$("#contentblock,#codeblock").toggle();});});'

    ⍝ add the footer to the bottom of the page
      Add #.Files.GetText server.Config.Root,'Styles\footer.txt'

    ⍝ add the header to the top of the page and wrap the body in a div with id="wrapper"
      Body.Push #.Files.GetText server.Config.Root,'Styles\banner.txt'

      Body.Push div'id="wrapper"'

    ⍝ set the language for the page
      lang←server.Config.Lang ⍝ use the language specified in Server.xml
      Head.SetAttr'lang="',lang,'" xml:lang="',lang,'" xmlns="http://www.w3.org/1999/xhtml"'
<<<<<<< HEAD

=======
>>>>>>> b6f6d5468c6f6f8e895285b9358e487843200a8d
    ⍝ call the base class Wrap function
      ⎕BASE.Wrap
    ∇

    ∇ r←Walk content;e
      r←⍬
      :For e :In content
          :If (#.HtmlElement.isClass e)∨#.HtmlElement.isInstance e
              r←r,⊃⎕CLASS e
              :Trap 6
                  r,←Walk e.Content
              :EndTrap
          :EndIf
      :EndFor
    ∇

:EndClass


Added Caption public field to Table

Added Caption public field to Table

:Class Table : #._html.table
⍝ Description:: Improved html table - accepts matrix of data
⍝ Constructor:: [data [cellAttr [headerRows [headerAttr [makeCellIds [makeRowIds]]]]]
⍝ data        - matrix of data to display in the table
⍝ cellAttr    - Cell Attributes
⍝ headerRows  - # of header rows
⍝ headerAttr  - Header attributes
⍝ makeCellIds - 1 to generate IDs
⍝ makeRowIds  - 1 to generate Row IDs

⍝ Public Fields::
⍝ Data        - matrix of data to display in the table
⍝ CellAttr    - Cell Attributes
⍝ HeaderRows  - # of header rows
⍝ HeaderAttr  - Header attributes
⍝ MakeCellIds - 1 to generate IDs
⍝ MakeRowIds  - 1 to generate Row IDs

⍝ Caption     - caption to be inserted at the top of the table

    :field public Data←0 0⍴⊂''
    :field public CellAttr←''
    :field public HeaderRows←0
    :field public HeaderAttr←''
    :field public MakeCellIds←0
    :field public MakeRowIds←0
    :Field public Caption←⍬

    ∇ Make0
      :Access public
      :Implements constructor
    ∇

    ∇ Make1 data
      :Access public
      :Implements constructor
      :If 1<⍴⍴data ⋄ data←,⊂data ⋄ :EndIf
      :If 1≥|≡data ⋄ data←,⊂1 1⍴⊂data ⋄ :EndIf
      (Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds)←data defaultArgs Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds
    ∇

    ∇ html←Render;data;rows;cols;hdrrows;head;body;headids;size;headattrs;rowids;bodyrows;bodyids;bodyattrs;caption
      :Access public
      SetId
      data←Data
      data←((rows←×/¯1↓⍴data),cols←¯1↑⍴data)⍴data
      hdrrows←rows⌊⍬⍴HeaderRows
      caption←head←body←''

      :If ⍬≢Caption
          caption←'
',(renderIt Caption),'
'
      :EndIf

      :If 0<hdrrows
          headids←headattrs←⊂''
          size←hdrrows,cols
          :If MakeCellIds
              headids←{' id="',(id,'_',∊'rc',¨⍕¨⍵),'"'}¨⍳size
          :EndIf
          :If ~0∊⍴HeaderAttr
              headattrs←size⍴{∊FormatAttr/¨1 ParseAttr ⍵}∘fmtAttr¨eis HeaderAttr
          :EndIf
          head←(eis headids,¨headattrs){('th',⍺)enc renderIt ⍵}¨hdrrows↑data
          rowids←⊂''
          :If MakeRowIds
              rowids←{' id="',id,'_row',(⍕⍵),'"'}¨⍳hdrrows
          :EndIf
          head←∊'thead'enc rowids{('tr',⍺)enc∊⍵}¨↓head
      :EndIf

      :If 0<bodyrows←(⊃⍴data)-hdrrows
          bodyids←bodyattrs←⊂''
          size←bodyrows,cols
          :If MakeCellIds
              bodyids←{' id="',(id,'_',∊'rc',¨⍕¨⍵),'"'}¨(⊂hdrrows,0)+¨⍳size
          :EndIf
          :If ~0∊⍴CellAttr
              bodyattrs←size⍴{∊FormatAttr/¨1 ParseAttr ⍵}¨fmtAttr¨eis CellAttr
          :EndIf
          body←(eis bodyids,¨bodyattrs){'',(renderIt ⍵),'	'}¨hdrrows↓data
          rowids←⊂''
          :If MakeRowIds
              rowids←{' id="',id,'_row',(⍕⍵),'"'}¨hdrrows+⍳bodyrows
          :EndIf
          body←∊'tbody'enc rowids{('tr',⍺)enc∊⍵}¨↓body
      :EndIf
      Content←caption,head,body
      html←⎕BASE.Render
    ∇

    ∇ attr←fmtAttr attr
      :If 2=⍴,attr  ⍝ 'attr' 'value' is never shorthanded (e.g. given special treatment for id/class)
      :AndIf 1∧.≥≡¨attr
      :AndIf ~∧/'='∊¨attr
          attr←,⊂attr
      :EndIf
    ∇

    enc←{'<',⍺,'>',⍵,'</',(⍺⍴⍨¯1+⍺⍳' '),'>'}

:EndClass

:Class Table : #._html.table
⍝ Description:: Improved html table - accepts matrix of data
⍝ Constructor:: [data [cellAttr [headerRows [headerAttr [makeCellIds [makeRowIds]]]]]
⍝ data        - matrix of data to display in the table
⍝ cellAttr    - Cell Attributes
⍝ headerRows  - # of header rows
⍝ headerAttr  - Header attributes
⍝ makeCellIds - 1 to generate IDs
⍝ makeRowIds  - 1 to generate Row IDs

⍝ Public Fields::
⍝ Data        - matrix of data to display in the table
⍝ CellAttr    - Cell Attributes
⍝ HeaderRows  - # of header rows
⍝ HeaderAttr  - Header attributes
⍝ MakeCellIds - 1 to generate IDs
⍝ MakeRowIds  - 1 to generate Row IDs

    :field public Data←0 0⍴⊂''
    :field public CellAttr←''
    :field public HeaderRows←0
    :field public HeaderAttr←''
    :field public MakeCellIds←0
    :field public MakeRowIds←0

    ∇ Make0
      :Access public
      :Implements constructor
    ∇

    ∇ Make1 data
      :Access public
      :Implements constructor
      :If 1<⍴⍴data ⋄ data←,⊂data ⋄ :EndIf
      :If 1≥|≡data ⋄ data←,⊂1 1⍴⊂data ⋄ :EndIf
      (Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds)←data defaultArgs Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds
    ∇

    ∇ html←Render;data;rows;cols;hdrrows;head;body;headids;size;headattrs;rowids;bodyrows;bodyids;bodyattrs
      :Access public
      SetId
      data←Data
      data←((rows←×/¯1↓⍴data),cols←¯1↑⍴data)⍴data
      hdrrows←rows⌊⍬⍴HeaderRows
      head←body←''

      :If 0<hdrrows
          headids←headattrs←⊂''
          size←hdrrows,cols
          :If MakeCellIds
              headids←{' id="',(id,'_',∊'rc',¨⍕¨⍵),'"'}¨⍳size
          :EndIf
          :If ~0∊⍴HeaderAttr
              headattrs←size⍴{∊FormatAttr/¨1 ParseAttr ⍵}∘fmtAttr¨eis HeaderAttr
          :EndIf
          head←(eis headids,¨headattrs){('th',⍺)enc renderIt ⍵}¨hdrrows↑data
          rowids←⊂''
          :If MakeRowIds
              rowids←{' id="',id,'_row',(⍕⍵),'"'}¨⍳hdrrows
          :EndIf
          head←∊'thead'enc rowids{('tr',⍺)enc∊⍵}¨↓head
      :EndIf

      :If 0<bodyrows←(⊃⍴data)-hdrrows
          bodyids←bodyattrs←⊂''
          size←bodyrows,cols
          :If MakeCellIds
              bodyids←{' id="',(id,'_',∊'rc',¨⍕¨⍵),'"'}¨(⊂hdrrows,0)+¨⍳size
          :EndIf
          :If ~0∊⍴CellAttr
              bodyattrs←size⍴{∊FormatAttr/¨1 ParseAttr ⍵}¨fmtAttr¨eis CellAttr
          :EndIf
          body←(eis bodyids,¨bodyattrs){'',(renderIt ⍵),'	'}¨hdrrows↓data
          rowids←⊂''
          :If MakeRowIds
              rowids←{' id="',id,'_row',(⍕⍵),'"'}¨hdrrows+⍳bodyrows
          :EndIf
          body←∊'tbody'enc rowids{('tr',⍺)enc∊⍵}¨↓body
      :EndIf
      Content←head,body
      html←⎕BASE.Render
    ∇

    ∇ attr←fmtAttr attr
      :If 2=⍴,attr  ⍝ 'attr' 'value' is never shorthanded (e.g. given special treatment for id/class)
      :AndIf 1∧.≥≡¨attr
      :AndIf ~∧/'='∊¨attr
          attr←,⊂attr
      :EndIf
    ∇

    enc←{'<',⍺,'>',⍵,'</',(⍺⍴⍨¯1+⍺⍳' '),'>'}

:EndClass


Add ∇to to APLToHTMLColor

Add ∇to to APLToHTMLColor

:Namespace HtmlUtils

    (⎕IO ⎕ML)←1
    ⎕FX 'r←CRLF' 'r←⎕UCS 13 10' ⍝ So it will be :Included
    enlist←{⎕ML←1 ⋄ ∊⍵} ⍝ APL2 enlist
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    ischar←{0 2∊⍨10|⎕DR⍵}
    quote←{0∊⍴⍵:'' ⋄ '"',({w←⍵⋄((w='"')/w)←⊂'\"'⋄enlist w}⍵),'"'}
    iotaz←{(⍴⍺){⍵×⍺≥⍵}⍺⍳⍵}
    innerhtml←{⊃↓/(⍵ iotaz'>')(-(⌽⍵)iotaz'<') ⍵}
    dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}
   ⍝ tonum←{0∊⍴⍵:⍬ ⋄ w←⍵ ⋄ ((w='-')/w)←'¯' ⋄ ⊃(//)⎕VFI w}

    ∇ r←atts Enclose innerhtml;i
    ⍝ Put an HTML tag around some HTML
      :If 1<|≡innerhtml ⋄ innerhtml←CRLF,enlist innerhtml,¨⊂CRLF ⋄ :EndIf
      :If 0∊⍴atts
          r←innerhtml
      :Else
          i←¯1+(atts←,atts)⍳' '
          r←'<',atts,'>',innerhtml,'</',(i↑atts),'>',CRLF
      :EndIf
    ∇

    ∇ r←{nl}Tag tag
    ⍝ Make a  self-closing tag
      nl←{6::1 ⋄ nl}⍬
      r←'<',tag,' />',nl/CRLF
    ∇

      FormatAttrs←{
      ⍝ format name/value pairs as tag attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name="value"'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          0∊⍴⍵:''
          {
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/'=',quote ⍵}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    ∇ r←tag GetAttr attr;attrs
      r←''
      :Trap 6
          →0⍴⍨0∊⍴tag←⎕XML tag
      :EndTrap
      attrs←(⊂1 4)⊃tag
      r←(attrs[;1]⍳⊂attr)⊃attrs[;2],⊂''
    ∇

      Styles←{
      ⍝ format name/value pairs as CSS style attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name: value'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          ⍺←''
          0∊⍴⍵:⍺,'{}'
          (0∊⍴⍺)↓⍺,{'{',({';'=¯1↑⍵:⍵ ⋄ ⍵,';'}⍵),'}'}{
              enlist{(×⍴⍺)/⍺,(×⍴⍵)/':',⍵,';'}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    _box←{⍺←1 ⋄ (⊂⍣(⍺=|≡⍵))⍵}
    _pifn←{({⍵''}⍣(1=|≡⍵))⍵}

    ∇ r←ScriptFollows;lines;pgm;from
     ⍝ Treat following commented lines in caller as a script, lines beginning with ⍝⍝ are stripped out
      :If 0∊⍴lines←(from←⎕IO⊃⎕RSI).⎕NR pgm←2⊃⎕SI
          lines←↓from.(180⌶)pgm
      :EndIf
      r←2↓∊CRLF∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}dtlb¨(1+2⊃⎕LC)↓lines
    ∇

    ∇ html←TextToHTML html;mask;CR
    ⍝ Add/insert
, replaces CR with
,CR
      :If ~0∊⍴html
          :If ∨/mask←html=CR←''⍴CRLF
              (mask/html)←⊂'
',CR
              html←enlist html
          :EndIf
          html,←(~∨/¯2↑mask)/'
',CRLF
      :EndIf
    ∇

    ∇ html←{fontsize}APLToHTML APL
    ⍝ returns APL code formatted for HTML
      fontsize←{6::'' ⋄ ';fontsize:',⍎⍵}'fontsize'
      :If 1<|≡APL ⋄ APL←enlist,∘CRLF¨APL ⋄ :EndIf
      :Trap 0
          html←3↓¯4↓'whitespace' 'preserve'⎕XML 1 3⍴0 'x'APL
      :Else
          html←APL
      :EndTrap
      html←('pre style="font-family:APL385 Unicode',fontsize,'"')Enclose CRLF,⍨html
    ∇

    ∇ html←APLToHTMLColor APL;types;colors;class;codes;apply;lines;head;tail;c;ent;to
     ⍝ returns APL code formatted for HTML with syntax coloring
       to←{(¯1↓⍺),((¯1+⊃⌽⍺)+⍳1+(⊃⍵)-(⊃⌽⍺)),1↓⍵}
      :Trap 0
          colors←⍬
          colors,←⊂'i200comment'(1 26 63)
          colors,←⊂'i200char'(4 29)
          colors,←⊂'i200num'(5 30)
          colors,←⊂'i200local'(10 32 35 53)
          colors,←⊂'i200global'(7 52 55)
          colors,←⊂'i200primitive'(19 44 146 to 153 214 to 221)
          colors,←⊂'i200idiom'(23 48)
          colors,←⊂'i200control'(58 155 to 179 181 to 213 222 to 248)
          colors,←⊂'i200space'(8 9 33 34)
          colors,←⊂'i200quad'(12 to 15 37 to 40)

          html←({(+/∨\' '≠⌽⍵)↑¨↓⍵}⍣(1≥|≡APL))APL ⍝ Make VTV if matrix
          lines←∊1↑¨⍨≢¨html
          types←0,0,⍨∊200⌶html                        ⍝ 200⌶ is color coding

          :For c ent :InEach '&<'('&' '<')
              ((c⍷∊html)/∊html)←⊂⊂ent
          :EndFor

          html←' ',' ',⍨⊃,/html
          :For class codes :In colors
              apply←1 0⍷types∊codes
              (apply/html)←(apply/html),¨⊂''
          :EndFor
          :For class codes :In colors
              apply←0 1⍷types∊codes
              (apply/html)←(apply/html),¨⊂''
          :EndFor
          head←1↓⊃html ⋄ tail←¯1↓⊃⌽html
          html←lines⊂1↓¯1↓html
          (⊃html),⍨←head ⋄ (⊃⌽html),←tail
          html←∊¨html
          html,⍨¨←,∘''¨↓↑('['∘,,∘']')¨⍕¨¯1+⍳⍴html ⍝ Prepend line numbers
          html←'pre'Enclose'code'Enclose html
      :Else
          html←APLToHTML APL
      :EndTrap
    ∇

      MakeStyle←{
          ⍺←''
          0∊⍴⍵:''
          (' ',⍨¯2↓enlist(eis ⍺),¨⊂', '),Styles ⍵
      }

    ∇ r←HtmlSafeText txt;i;m;u;ucs;s
    ⍝ make text HTML "safe"
      r←,⎕FMT txt
      i←'&<>"#'⍳r
      i-←(i=1)∧1↓(i=5),0 ⍝ mark & that aren't &#
      m←i∊⍳4
      u←127<ucs←⎕UCS r
      s←' '=r
      (s/r)←⊂' '
      (m/r)←('&' '<' '>' '"')[m/i]
      (u/r)←(~∘' ')¨↓'G<&#ZZZ9;>'⎕FMT u/ucs
      r←enlist r
    ∇

:EndNamespace

:Namespace HtmlUtils

    (⎕IO ⎕ML)←1
    ⎕FX 'r←CRLF' 'r←⎕UCS 13 10' ⍝ So it will be :Included
    enlist←{⎕ML←1 ⋄ ∊⍵} ⍝ APL2 enlist
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    ischar←{0 2∊⍨10|⎕DR⍵}
    quote←{0∊⍴⍵:'' ⋄ '"',({w←⍵⋄((w='"')/w)←⊂'\"'⋄enlist w}⍵),'"'}
    iotaz←{(⍴⍺){⍵×⍺≥⍵}⍺⍳⍵}
    innerhtml←{⊃↓/(⍵ iotaz'>')(-(⌽⍵)iotaz'<') ⍵}
    dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}
   ⍝ tonum←{0∊⍴⍵:⍬ ⋄ w←⍵ ⋄ ((w='-')/w)←'¯' ⋄ ⊃(//)⎕VFI w}

    ∇ r←atts Enclose innerhtml;i
    ⍝ Put an HTML tag around some HTML
      :If 1<|≡innerhtml ⋄ innerhtml←CRLF,enlist innerhtml,¨⊂CRLF ⋄ :EndIf
      :If 0∊⍴atts
          r←innerhtml
      :Else
          i←¯1+(atts←,atts)⍳' '
          r←'<',atts,'>',innerhtml,'</',(i↑atts),'>',CRLF
      :EndIf
    ∇

    ∇ r←{nl}Tag tag
    ⍝ Make a  self-closing tag
      nl←{6::1 ⋄ nl}⍬
      r←'<',tag,' />',nl/CRLF
    ∇

      FormatAttrs←{
      ⍝ format name/value pairs as tag attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name="value"'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          0∊⍴⍵:''
          {
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/'=',quote ⍵}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    ∇ r←tag GetAttr attr;attrs
      r←''
      :Trap 6
          →0⍴⍨0∊⍴tag←⎕XML tag
      :EndTrap
      attrs←(⊂1 4)⊃tag
      r←(attrs[;1]⍳⊂attr)⊃attrs[;2],⊂''
    ∇

      Styles←{
      ⍝ format name/value pairs as CSS style attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name: value'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          ⍺←''
          0∊⍴⍵:⍺,'{}'
          (0∊⍴⍺)↓⍺,{'{',({';'=¯1↑⍵:⍵ ⋄ ⍵,';'}⍵),'}'}{
              enlist{(×⍴⍺)/⍺,(×⍴⍵)/':',⍵,';'}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    _box←{⍺←1 ⋄ (⊂⍣(⍺=|≡⍵))⍵}
    _pifn←{({⍵''}⍣(1=|≡⍵))⍵}

    ∇ r←ScriptFollows;lines;pgm;from
     ⍝ Treat following commented lines in caller as a script, lines beginning with ⍝⍝ are stripped out
      :If 0∊⍴lines←(from←⎕IO⊃⎕RSI).⎕NR pgm←2⊃⎕SI
          lines←↓from.(180⌶)pgm
      :EndIf
      r←2↓∊CRLF∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}dtlb¨(1+2⊃⎕LC)↓lines
    ∇

    ∇ html←TextToHTML html;mask;CR
    ⍝ Add/insert
, replaces CR with
,CR
      :If ~0∊⍴html
          :If ∨/mask←html=CR←''⍴CRLF
              (mask/html)←⊂'
',CR
              html←enlist html
          :EndIf
          html,←(~∨/¯2↑mask)/'
',CRLF
      :EndIf
    ∇

    ∇ html←{fontsize}APLToHTML APL
    ⍝ returns APL code formatted for HTML
      fontsize←{6::'' ⋄ ';fontsize:',⍎⍵}'fontsize'
      :If 1<|≡APL ⋄ APL←enlist,∘CRLF¨APL ⋄ :EndIf
      :Trap 0
          html←3↓¯4↓'whitespace' 'preserve'⎕XML 1 3⍴0 'x'APL
      :Else
          html←APL
      :EndTrap
      html←('pre style="font-family:APL385 Unicode',fontsize,'"')Enclose CRLF,⍨html
    ∇

    ∇ html←APLToHTMLColor APL;types;colors;class;codes;apply;lines;head;tail;c;ent
     ⍝ returns APL code formatted for HTML with syntax coloring
      :Trap 0
          colors←⍬
          colors,←⊂'i200comment'(1 26 63)
          colors,←⊂'i200char'(4 29)
          colors,←⊂'i200num'(5 30)
          colors,←⊂'i200local'(10 32 35 53)
          colors,←⊂'i200global'(7 52 55)
          colors,←⊂'i200primitive'(19 44 146 To 153 214 To 221)
          colors,←⊂'i200idiom'(23 48)
          colors,←⊂'i200control'(58 155 To 179 181 To 213 222 To 248)
          colors,←⊂'i200space'(8 9 33 34)
          colors,←⊂'i200quad'(12 To 15 37 To 40)

          html←({(+/∨\' '≠⌽⍵)↑¨↓⍵}⍣(1≥|≡APL))APL ⍝ Make VTV if matrix
          lines←∊1↑¨⍨≢¨html
          types←0,0,⍨∊200⌶html                        ⍝ 200⌶ is color coding

          :For c ent :InEach '&<'('&' '<')
              ((c⍷∊html)/∊html)←⊂⊂ent
          :EndFor

          html←' ',' ',⍨⊃,/html
          :For class codes :In colors
              apply←1 0⍷types∊codes
              (apply/html)←(apply/html),¨⊂''
          :EndFor
          :For class codes :In colors
              apply←0 1⍷types∊codes
              (apply/html)←(apply/html),¨⊂''
          :EndFor
          head←1↓⊃html ⋄ tail←¯1↓⊃⌽html
          html←lines⊂1↓¯1↓html
          (⊃html),⍨←head ⋄ (⊃⌽html),←tail
          html←∊¨html
          html,⍨¨←,∘''¨↓↑('['∘,,∘']')¨⍕¨¯1+⍳⍴html ⍝ Prepend line numbers
          html←'pre'Enclose'code'Enclose html
      :Else
          html←APLToHTML APL
      :EndTrap
    ∇

      MakeStyle←{
          ⍺←''
          0∊⍴⍵:''
          (' ',⍨¯2↓enlist(eis ⍺),¨⊂', '),Styles ⍵
      }

    ∇ r←HtmlSafeText txt;i;m;u;ucs;s
    ⍝ make text HTML "safe"
      r←,⎕FMT txt
      i←'&<>"#'⍳r
      i-←(i=1)∧1↓(i=5),0 ⍝ mark & that aren't &#
      m←i∊⍳4
      u←127<ucs←⎕UCS r
      s←' '=r
      (s/r)←⊂' '
      (m/r)←('&' '<' '>' '"')[m/i]
      (u/r)←(~∘' ')¨↓'G<&#ZZZ9;>'⎕FMT u/ucs
      r←enlist r
    ∇

:EndNamespace


? also displays options

? also displays options

 output←regexes(op Run args)input;from;to;options;lines;trans;Expand;repeat;s;r;last;fns;Quote;q;hasrepeat;postproc;combine

 :Trap 11 22 ⍝ accept filename or actual input
     input←input ⎕NTIE 0
 :EndTrap

 :Trap 11 22 ⍝ accept filename or actual regex
     regexes←⊃⎕NGET regexes 1
 :EndTrap

 repeat←⍎{×≢⍵:⍵ ⋄ '1'}args∩'≡',⎕D ⍝ extract repetition, if any, else 1
 hasrepeat←∨/args∊⍨'≡',⎕D
 ⍝ select options:
 options←('ResultText' 'Simple')('Greedy' 0)('IC' 1)('Mode' 'D')('Mode' 'M')('DotAll' '1')('UCP' 1)('OM' 1)/⍨1,'gidmauo'∊819⌶args~' -'
 s r←(∨/∊∘op)¨'Ss' 'Rr'
 options↓⍨←s ⍝ ResultText is not for ⎕S

 fns←+/∧\'⍵'∊¨regexes
 postproc←∊'{⍵}','∘{'∘,¨,∘'}'¨fns↑regexes ⍝ post-processing functions
 regexes↓⍨←fns

 last←⊃⌽regexes
 :If 2|≢regexes ⍝ odd number of strings → many-to-one
 :OrIf '⍵'∊last ⍝ transformation function → many-to-function
     from←¯1↓regexes ⍝ remove transformation pattern/function
     :If ~'⍵'∊last ⍝ no ⍵ → pattern
         Expand←'⍵B' '⍵b' '⍵P' '⍵p' '⍵M' '⍵O' '⍵L' '⍵N'⎕R'⍵.Block' '⍵.BlockNum' '⍵.Pattern' '⍵.PatternNum' '⍵.Match' '⍵.Offsets' '⍵.Lengths' '⍵.Names'
     :OrIf ~≡⎕FX'to←{'(r/',⍕{')(Expand last)(r/'}⍵')'}' ⍝ if definition fails, revert to pattern (allows ⍵ in pattern)
         to←⊃⌽regexes
     :EndIf
 :Else
     lines←2÷⍨≢regexes
     from←lines↑regexes
     to←lines↓regexes
 :EndIf

 combine←'{⊃,/⍵↑¨⍨⌈/≢¨⍵}'
 q←⎕NS ⍬
 :If s r∨.∧'?'∊args
     Quote←{q,q,⍨⍵/⍨1+⍵=q←''''}
     q.from←⍕Quote¨from
     :If 3=⎕NC'to'
         q.to←1⌽'  ',3↓∊⎕NR'to'
     :Else
         q.to←⍕Quote¨to
     :EndIf
     :If 3=⎕NC'repeat'
         q.repeat←'⍣',∊⎕NR'repeat'
     :ElseIf 1≢repeat
         q.repeat←'⍣',⍕repeat
     :Else
         q.repeat←''
     :EndIf
     :If ×≢options
         q.options←'⍠',∊{' (',(Quote⊃⍵),' ',({0 1∊⍨⊂⍵:⍕⍵ ⋄ '''',⍵,''''}⊃⌽⍵),')'}¨options
     :Else
         q.options←''
     :EndIf
     q.op←s r/'SR'
     q.postproc←4↓postproc
     q.postproc,←(hasrepeat∧s)/(''≡q.postproc)↓'∘',combine
     output←q.(postproc,from,'⎕',op,to,options,repeat)
 :Else
     :Trap 6 11
         :If r
             output←from ⎕R to⍠options⍣repeat⊢input
         :ElseIf s
             output←from ⎕S to⍠options⍣repeat⊢input
             output←(⍎combine)⍣hasrepeat⊢output ⍝ merge search results if we need to continue
         :EndIf
         output←(⍎postproc)output
     :Case 6 ⍝ no output
         output←'*** Left operand must be ''R'' or ''S'' ***'
     :Case 11 ⍝ faulty regex
         output←'*** ',⎕DMX.Message,' ─ try adding ? to the Arguments to see the attempted APL expression ***'
     :EndTrap
 :EndIf

 output←regexes(op Run args)input;from;to;options;lines;trans;Expand;repeat;s;r;last;fns;Quote;q;hasrepeat;postproc;combine

 :Trap 11 22 ⍝ accept filename or actual input
     input←input ⎕NTIE 0
 :EndTrap

 :Trap 11 22 ⍝ accept filename or actual regex
     regexes←⊃⎕NGET regexes 1
 :EndTrap

 repeat←⍎{×≢⍵:⍵ ⋄ '1'}args∩'≡',⎕D ⍝ extract repetition, if any, else 1
 hasrepeat←∨/args∊⍨'≡',⎕D
 ⍝ select options:
 options←('ResultText' 'Simple')('Greedy' 0)('IC' 1)('Mode' 'D')('Mode' 'M')('DotAll' '1')('UCP' 1)('OM' 1)/⍨1,'gidmauo'∊819⌶args~' -'
 s r←(∨/∊∘op)¨'Ss' 'Rr'
 options↓⍨←s ⍝ ResultText is not for ⎕S

 fns←+/∧\'⍵'∊¨regexes
 postproc←∊'{⍵}','∘{'∘,¨,∘'}'¨fns↑regexes ⍝ post-processing functions
 regexes↓⍨←fns

 last←⊃⌽regexes
 :If 2|≢regexes ⍝ odd number of strings → many-to-one
 :OrIf '⍵'∊last ⍝ transformation function → many-to-function
     from←¯1↓regexes ⍝ remove transformation pattern/function
     :If ~'⍵'∊last ⍝ no ⍵ → pattern
         Expand←'⍵B' '⍵b' '⍵P' '⍵p' '⍵M' '⍵O' '⍵L' '⍵N'⎕R'⍵.Block' '⍵.BlockNum' '⍵.Pattern' '⍵.PatternNum' '⍵.Match' '⍵.Offsets' '⍵.Lengths' '⍵.Names'
     :OrIf ~≡⎕FX'to←{'(r/',⍕{')(Expand last)(r/'}⍵')'}' ⍝ if definition fails, revert to pattern (allows ⍵ in pattern)
         to←⊃⌽regexes
     :EndIf
 :Else
     lines←2÷⍨≢regexes
     from←lines↑regexes
     to←lines↓regexes
 :EndIf

 combine←'{⊃,/⍵↑¨⍨⌈/≢¨⍵}'
 q←⎕NS ⍬
 :If s r∨.∧'?'∊args
     Quote←{q,q,⍨⍵/⍨1+⍵=q←''''}
     q.from←⍕Quote¨from
     :If 3=⎕NC'to'
         q.to←1⌽'  ',3↓∊⎕NR'to'
     :Else
         q.to←⍕Quote¨to
     :EndIf
     :If 3=⎕NC'repeat'
         q.repeat←'⍣',∊⎕NR'repeat'
     :ElseIf 1≢repeat
         q.repeat←'⍣',⍕repeat
     :Else
         q.repeat←''
     :EndIf
     q.op←s r/'SR'
     q.postproc←4↓postproc
     q.postproc,←(hasrepeat∧s)/(''≡q.postproc)↓'∘',combine
     output←q.(postproc,from,'⎕',op,to,repeat)
 :Else
     :Trap 6 11
         :If r
             output←from ⎕R to⍠options⍣repeat⊢input
         :ElseIf s
             output←from ⎕S to⍠options⍣repeat⊢input
             output←(⍎combine)⍣hasrepeat⊢output ⍝ merge search results if we need to continue
         :EndIf
         output←(⍎postproc)output
     :Case 6 ⍝ no output
         output←'*** Left operand must be ''R'' or ''S'' ***'
     :Case 11 ⍝ faulty regex
         output←'*** ',⎕DMX.Message,' ─ try adding ? to the Arguments to see the attempted APL expression ***'
     :EndTrap
 :EndIf


CSS typo fontsize → font-size

CSS typo fontsize → font-size

:Namespace HTMLInput

    (⎕IO ⎕ML)←1
    ⎕FX 'r←CRLF' 'r←⎕UCS 13 10' ⍝ So it will be :Included
    enlist←{∊⍵} ⍝ APL2 enlist
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    ischar←{0 2∊⍨10|⎕DR⍵}
    quote←{0∊⍴⍵:'' ⋄ '"',({w←⍵⋄((w='"')/w)←⊂'\"'⋄enlist w}⍵),'"'}
    iotaz←{(⍴⍺){⍵×⍺≥⍵}⍺⍳⍵}
    innerhtml←{⊃↓/(⍵ iotaz'>')(-(⌽⍵)iotaz'<') ⍵}
    dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}

    ∇ r←atts Enclose innerhtml;i
    ⍝ Put an HTML tag around some HTML
      :If 1<|≡innerhtml ⋄ innerhtml←CRLF,enlist innerhtml,¨⊂CRLF ⋄ :EndIf
      :If 0∊⍴atts
          r←innerhtml
      :Else
          i←¯1+(atts←,atts)⍳' '
          r←'<',atts,'>',innerhtml,'</',(i↑atts),'>',CRLF
      :EndIf
    ∇

    ∇ r←{nl}Tag tag
    ⍝ Make a  self-closing tag
      nl←{6::1 ⋄ nl}⍬
      r←'<',tag,' />',nl/CRLF
    ∇

      Attrs←{
      ⍝ format name/value pairs as tag attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name="value"'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          0∊⍴⍵:''
          {
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/'=',quote ⍵}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    ∇ r←tag GetAttr attr;attrs
      r←''
      :Trap 6
          →0⍴⍨0∊⍴tag←⎕XML tag
      :EndTrap
      attrs←(⊂1 4)⊃tag
      r←(attrs[;1]⍳⊂attr)⊃attrs[;2],⊂''
    ∇

      Styles←{
      ⍝ format name/value pairs as CSS style attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name: value'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          ⍺←''
          0∊⍴⍵:''
          (0∊⍴⍺)↓⍺,' ',{'{',({';'=¯1↑⍵:⍵ ⋄ ⍵,';'}⍵),' }'}{
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/': ',⍵,';'}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    _box←{⍺←1 ⋄ (⊂⍣(⍺=|≡⍵))⍵}
    _pifn←{({⍵''}⍣(1=|≡⍵))⍵}

    ∇ r←ScriptFollows;lines;pgm;from
     ⍝ Treat following commented lines in caller as a script, lines beginning with ⍝⍝ are stripped out
      :If 0∊⍴lines←(from←⎕IO⊃⎕RSI).⎕NR pgm←2⊃⎕SI
          lines←↓from.(180⌶)pgm
      :EndIf
      r←∊CRLF∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}dtlb¨(1+2⊃⎕LC)↓lines
    ∇

    ∇ html←TextToHTML html;mask;CR
    ⍝ Add/insert
, replaces CR with
,CR
      :If ~0∊⍴html
          :If ∨/mask←html=CR←''⍴CRLF
              (mask/html)←⊂'
',CR
              html←enlist html
          :EndIf
          html,←(~∨/¯2↑mask)/'
',CRLF
      :EndIf
    ∇

    ∇ html←{fontsize}APLToHTML APL
    ⍝ returns APL code formatted for HTML
      fontsize←{6::'' ⋄ ';font-size:',⍎⍵}'fontsize'
      :If 1<|≡APL ⋄ APL←enlist,∘CRLF¨APL ⋄ :EndIf
      :Trap 0
          html←3↓¯4↓'whitespace' 'preserve'⎕XML 1 3⍴0 'x'APL
      :Else
          html←APL
      :EndTrap
      html←('pre style="font-family:APL385 Unicode',fontsize,'"')Enclose'code'Enclose CRLF,⍨html
    ∇

    ∇ html←{fontsize}APLToHTMLColour APL;types;spans;colour;mask;⎕ML
     ⍝ returns APL code formatted for HTML with syntax colouring
     ⍝ Uses global APLToHTMLColourScheme e.g. ('blue' 19 23)('green' 1)('gray' 32)('red' 4)...
      ⎕ML←1
      fontsize←{6::'' ⋄ ';font-size:',⍎⍵}'fontsize'
      :Trap 0
          :If 0=⎕NC'APLToHTMLColourScheme'
              APLToHTMLColourScheme←,⊂'blue' 17 19 23 48 56 57 146,(147+⍳5),214,(215+⍳5)
              APLToHTMLColourScheme,←⊂'darkgreen' 1 26
              APLToHTMLColourScheme,←⊂'brown' 6 16 31 32 41 43 46 49,(118+⍳27),(152+⍳34),(186+⍳27),(220+⍳34)
              APLToHTMLColourScheme,←⊂'darkblue' 4 12 13 14
              APLToHTMLColourScheme,←⊂'purple' 37 39
              APLToHTMLColourScheme,←⊂'pink',(50+⍳3)
              APLToHTMLColourScheme,←⊂'red' 9 11 15 34 36 40 42⊣,(186+⍳68)
              APLToHTMLColourScheme,←⊂'gray' 10 35 47 50
          :EndIf
          html←({(+/∨\' '≠⌽⍵)↑¨↓⍵}⍣(1≥|≡APL))APL ⍝ Make VTV if matrix
          html,¨⍨←⎕UCS 13                        ⍝ Mark line beginnings
          types←∊200⌶html                        ⍝ Colour coding
          html←∊html
          spans←1,2≠/types                       ⍝ Colour spans
          html⊂⍨←spans
          types/⍨←spans
          :For colour :In APLToHTMLColourScheme ⍝ 'Colour' CodeElementNumber CodeElementNumber ...
              mask←types∊1↓colour
              (mask/html)←{(''),(HtmlSafeText ⍵),''}¨mask/html
          :EndFor
          html←∊html          ⍝ Encorporate tags
          html⊂⍨←html=⎕UCS 13 ⍝ Restore lines
          html↓¨⍨←1           ⍝ Remove line markers
          html,⍨¨←↓↑('['∘,,∘'] ')¨⍕¨¯1+⍳⍴html ⍝ Prepend blue line numbers
          html←('pre style="font-family:APL385 Unicode',fontsize,'"')Enclose'code'Enclose CRLF,⍨∊,∘CRLF¨html
      :Else
          html←fontsize APLToHTML APL
      :EndTrap
    ∇

    ∇ html←{id}Anchor pars;href;title;target;other;content
    ⍝ Builds an anchor  tag
    ⍝ pars: content {href} {title} {target} {other_attrs}
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      pars←eis pars
      content href title target other←5↑pars,(⍴pars)↓'' '' '' '' ''
      html←('a',(Attrs'id' 'href' 'title' 'target'{(⍵ ine ⍺)⍵}¨id href title target),Attrs other)Enclose content
    ∇

    ∇ html←{n}BRA html
    ⍝ (BReak After) Add n
's after html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html,←n{(⍺×⍴⍵)⍴⍵}'
'
    ∇

    ∇ html←{n}BR html
    ⍝ (BReak) Add n
's before html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html←(n{(⍺×⍴⍵)⍴⍵}'
'),html
    ∇

    ∇ html←{n}SP html
    ⍝ (SPace) Add n non-breaking spaces   before html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html←(n{(⍺×⍴⍵)⍴⍵}' '),html
    ∇

    ∇ html←{n}SPA html
    ⍝ (SPace) Add n non-breaking spaces   after html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html,←n{(⍺×⍴⍵)⍴⍵}' '
    ∇

    ∇ r←{src}JS script
    ⍝ Javascript
    ⍝ script: javascript script if no src or filename if src=1
      :If 0≠⎕NC'src' ⋄ r←('script src=',quote src)Enclose''
      :Else ⋄ r←'script'Enclose script
      :EndIf
    ∇

    ∇ r←{name}DropDown pars;values;display;gv;mask;n
    ⍝ par - items selected_value(s) attributes sort
    ⍝ if items is 2 column matrix, [;1] is values, [;2] is display
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      :If 2=⍴⍴pars ⋄ pars←,⊂pars ⋄ :EndIf
      pars←pars,(⍴pars)↓('Item1' 'Item2')'Item1' '' 0
      :If ~ischar 1⊃pars ⋄ pars[1]←⊂⍕¨1⊃pars ⋄ :EndIf
      :If 1=⍴⍴1⊃pars ⋄ values display←pars[1]
      :Else ⋄ values display←↓[1]1⊃pars
      :EndIf
      mask←values∊⍕¨eis 2⊃pars
      :If 4⊃pars ⋄ gv←⍒mask ⋄ values←values[gv] ⋄ display←display[gv] ⋄ mask←mask[gv] ⋄ :EndIf ⍝ Sort selected items first
      r←'<select ',(3⊃pars),(name ine' id=',n,' name=',n←quote name),'>',CRLF
      r,←enlist(mask{'<option value="',(⍕⍵),'"',(⍺/' selected="selected"'),'>'}¨values),¨display,¨⊂'</option>',CRLF
      r,←'</select>',CRLF
    ∇

    ∇ r←{atts}(method Form)innerhtml
      :If 9=⎕NC'atts' ⋄ atts←'action="',atts.Page,'"' ⍝ MildPage calling
      :ElseIf 0=⎕NC'atts' ⋄ atts←'' ⋄ :EndIf
      atts,←' method="',method,'"'
      atts,←('post'≡#.Strings.lc method)/' enctype="multipart/form-data"'
      r←('form ',atts)Enclose innerhtml
    ∇

    ∇ r←lab Label tag;id
      id←tag GetAttr'id'
      r←tag,⍨('label',id ine' for="',id,'"')Enclose lab
    ∇

    ∇ r←{name}Edit pars;att;size;val
    ⍝ pars: [value] [size] [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      val size att←3↑pars,(⍴pars)↓''⍬''
      r←Tag'input ',att,((~0∊⍴size)/' size="',(⍕size),'"'),' type="text" value="',(⍕val),'"',name
    ∇

    ∇ r←{name}Submit pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'Submit' ''
      r←Tag'input type="submit" ',(2⊃pars),' value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Button pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'Button' ''
      r←Tag'input type="button" ',(2⊃pars),' value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}File pars
    ⍝ pars: [value [size [attributes]]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓''⍬''
      r←Tag'input ',(3⊃pars),' type="file" size="',(⍕2⊃pars),'" value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Hidden pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'' ''
      r←Tag'input ',(2⊃pars),' type="hidden" value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Password pars;att;size;val
    ⍝ pars: [value] [size] [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      val size att←3↑pars,(⍴pars)↓''⍬''
      r←Tag'input ',att,((~0∊⍴size)/' size="',(⍕size),'"'),' type="password" value="',val,'"',name
    ∇

    ∇ r←{name}CheckBox pars
    ⍝ pars: checked [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←eis pars
      pars←pars,(⍴pars)↓0 ''
      r←Tag'input ',(2⊃pars),' type="checkbox"',name,(0≠1⊃pars)/' checked="checked"'
    ∇

    ∇ r←{name}CheckBoxGroup pars;choices;sel
    ⍝ pars: values/texts
      name←{6::'' ⋄ enlist(,∘('="',name,'"'))¨' id' ' name'}⍬
      :If 2=|≡pars ⋄ choices←pars ⋄ sel←⍬
      :Else ⋄ (choices sel)←pars
      :EndIf
      :If 1=⍴⍴choices ⋄ choices←choices,[1.1]choices ⋄ :EndIf
      sel←(⊃⍴choices)↑sel
      r←enlist name∘{BRA(0 Tag'input type="checkbox"',⍺,' value="',(1⊃⍵),'"',(1=3⊃⍵)/' checked="checked"'),(2⊃⍵)}¨↓choices,sel
    ∇

    ∇ r←{name}RadioButton pars
    ⍝ pars: checked [value [attributes]]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←eis pars
      pars←pars,(⍴,pars)↓0 name''
      r←Tag'input ',(3⊃pars),' type="radio" value="',(2⊃pars),'"',name,(0≠1⊃pars)/' checked="checked"'
    ∇

    ∇ r←{name}RadioButtonGroup pars;choices;sel
    ⍝ pars: values/texts [sel]
      name←{6::'' ⋄ enlist(,∘('="',name,'"'))¨' id' ' name'}⍬
      :If 2=|≡pars ⋄ choices←pars ⋄ sel←⍬
      :Else ⋄ (choices sel)←pars
      :EndIf
      :If 1=⍴⍴choices ⋄ choices←choices,[1.1]choices ⋄ :EndIf
      sel←(⊃⍴choices)↑sel
      r←enlist name∘{BRA(0 Tag'input type="radio"',⍺,' value="',(1⊃⍵),'"',(1=3⊃⍵)/' checked="checked"'),(2⊃⍵)}¨↓choices,sel
    ∇

    ∇ r←{id}Table pars;x;data;atts;tdc;tda;thc;tha;hdrrows;z;mask;cellids;n;rowids;rows
    ⍝ pars: data table_atts cell_attribs header_attribs #header_rows cell-ids? row-ids?
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      :If 2=⍴⍴pars ⋄ pars←,⊂pars ⋄ :EndIf ⍝ Matrix of data only
      pars←pars,(⍴,pars)↓(1 1⍴⊂'data')'' '' '' 0 0 0
      data atts tda tha hdrrows cellids rowids←pars
      hdrrows←⍬⍴hdrrows
      data←((rows←×/¯1↓⍴data),¯1↑⍴data)⍴data
      x←⍕¨data
      :If cellids ⍝ do we add cell ids?
          r←(' id="'∘,∘(,∘'">')¨{enlist'rc',¨⍕¨⍵}¨⍳⍴data),¨x,¨⊂'	'
      :Else
          r←'>',¨x,¨⊂'	'
      :EndIf
      :If (⍴tda)≡⍴data
          r←('<td'∘,¨tda ine¨' ',¨tda),¨r
      :ElseIf (⍴tda)=¯1↑⍴data
          r←↑(⊂'<td'∘,¨tda ine¨' ',¨tda),¨¨↓r
      :Else
          r←('<td',tda ine' ',tda)∘,¨r
      :EndIf
      :If z←0≠hdrrows ⋄ r[⍳hdrrows;]←(⊂''),¨x[⍳hdrrows;],¨⊂'	' ⋄ :EndIf
      :If rowids ⍝ if using row ids, add them for non-header rows
          r←(⍳rows){'
',⍵,'
',CRLF}¨,/r
      :Else
          r←'
'∘,¨(,/r),¨⊂'
',CRLF
      :EndIf
      :If z ⋄ mask←(4+⍴r)⍴1 ⋄ mask[n←1,(2 3+hdrrows),⍴mask]←0
          r←mask\r
          r[n]←'
' '
' '
' '
',¨⊂CRLF
      :EndIf
      r←('table',(id ine' id="',id,'"'),(atts ine' ',atts))Enclose CRLF,(enlist r),CRLF
    ∇

    ∇ r←{name}List pars;items;ordered;t
    ⍝ pars: items [ordered? 0/1]
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      :Select ≡pars
      :CaseList 1 ¯2 ⋄ ordered←¯1↑pars ⋄ items←eis ¯1↓pars
      :Case 2 ⋄ ordered←0 ⋄ items←pars
      :Else ⋄ (items ordered)←2↑pars,(⍴,pars)↓'' 0
      :EndSelect
      t←(1+ordered)⊃'ul' 'ol'
      r←(t,name ine' id=',quote name)Enclose enlist'li'∘Enclose¨items
    ∇

    ∇ r←{name}MultiEdit pars
    ⍝ pars: (rows cols) [values [attributes]]
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      pars←pars,(⍴pars)↓(10 40)'' ''
      r←'textarea ',(3⊃pars),(name ine' id="',name,'" name="',name,'"'),' rows="',(⍕1 1⊃pars),'" cols="',(⍕1 2⊃pars),'"'
      r←r Enclose CRLF,(2⊃pars),CRLF
    ∇

      MakeStyle←{
          ⍺←''
          0∊⍴⍵:''
          (' ',⍨¯2↓enlist(eis ⍺),¨⊂', '),Styles ⍵
      }

    ∇ r←HtmlSafeText txt;i;m;u;ucs
    ⍝ make text HTML "safe"
      r←,⎕FMT txt
      i←'&<>"#'⍳r
      i-←(i=1)∧1↓(i=5),0 ⍝ mark & that aren't &#
      m←i∊⍳4
      u←127<ucs←⎕UCS r
      (m/r)←('&' '<' '>' '"')[m/i]
      (u/r)←(~∘' ')¨↓'G<&#ZZZ9;>'⎕FMT u/ucs
      r←enlist r
    ∇

:EndNamespace

:Namespace HTMLInput

    (⎕IO ⎕ML)←1
    ⎕FX 'r←CRLF' 'r←⎕UCS 13 10' ⍝ So it will be :Included
    enlist←{∊⍵} ⍝ APL2 enlist
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    ischar←{0 2∊⍨10|⎕DR⍵}
    quote←{0∊⍴⍵:'' ⋄ '"',({w←⍵⋄((w='"')/w)←⊂'\"'⋄enlist w}⍵),'"'}
    iotaz←{(⍴⍺){⍵×⍺≥⍵}⍺⍳⍵}
    innerhtml←{⊃↓/(⍵ iotaz'>')(-(⌽⍵)iotaz'<') ⍵}
    dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}

    ∇ r←atts Enclose innerhtml;i
    ⍝ Put an HTML tag around some HTML
      :If 1<|≡innerhtml ⋄ innerhtml←CRLF,enlist innerhtml,¨⊂CRLF ⋄ :EndIf
      :If 0∊⍴atts
          r←innerhtml
      :Else
          i←¯1+(atts←,atts)⍳' '
          r←'<',atts,'>',innerhtml,'</',(i↑atts),'>',CRLF
      :EndIf
    ∇

    ∇ r←{nl}Tag tag
    ⍝ Make a  self-closing tag
      nl←{6::1 ⋄ nl}⍬
      r←'<',tag,' />',nl/CRLF
    ∇

      Attrs←{
      ⍝ format name/value pairs as tag attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name="value"'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          0∊⍴⍵:''
          {
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/'=',quote ⍵}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    ∇ r←tag GetAttr attr;attrs
      r←''
      :Trap 6
          →0⍴⍨0∊⍴tag←⎕XML tag
      :EndTrap
      attrs←(⊂1 4)⊃tag
      r←(attrs[;1]⍳⊂attr)⊃attrs[;2],⊂''
    ∇

      Styles←{
      ⍝ format name/value pairs as CSS style attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name: value'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          ⍺←''
          0∊⍴⍵:''
          (0∊⍴⍺)↓⍺,' ',{'{',({';'=¯1↑⍵:⍵ ⋄ ⍵,';'}⍵),' }'}{
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/': ',⍵,';'}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    _box←{⍺←1 ⋄ (⊂⍣(⍺=|≡⍵))⍵}
    _pifn←{({⍵''}⍣(1=|≡⍵))⍵}

    ∇ r←ScriptFollows;lines;pgm;from
     ⍝ Treat following commented lines in caller as a script, lines beginning with ⍝⍝ are stripped out
      :If 0∊⍴lines←(from←⎕IO⊃⎕RSI).⎕NR pgm←2⊃⎕SI
          lines←↓from.(180⌶)pgm
      :EndIf
      r←∊CRLF∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}dtlb¨(1+2⊃⎕LC)↓lines
    ∇

    ∇ html←TextToHTML html;mask;CR
    ⍝ Add/insert
, replaces CR with
,CR
      :If ~0∊⍴html
          :If ∨/mask←html=CR←''⍴CRLF
              (mask/html)←⊂'
',CR
              html←enlist html
          :EndIf
          html,←(~∨/¯2↑mask)/'
',CRLF
      :EndIf
    ∇

    ∇ html←{fontsize}APLToHTML APL
    ⍝ returns APL code formatted for HTML
      fontsize←{6::'' ⋄ ';fontsize:',⍎⍵}'fontsize'
      :If 1<|≡APL ⋄ APL←enlist,∘CRLF¨APL ⋄ :EndIf
      :Trap 0
          html←3↓¯4↓'whitespace' 'preserve'⎕XML 1 3⍴0 'x'APL
      :Else
          html←APL
      :EndTrap
      html←('pre style="font-family:APL385 Unicode',fontsize,'"')Enclose'code'Enclose CRLF,⍨html
    ∇

    ∇ html←{fontsize}APLToHTMLColour APL;types;spans;colour;mask;⎕ML
     ⍝ returns APL code formatted for HTML with syntax colouring
     ⍝ Uses global APLToHTMLColourScheme e.g. ('blue' 19 23)('green' 1)('gray' 32)('red' 4)...
      ⎕ML←1
      fontsize←{6::'' ⋄ ';fontsize:',⍎⍵}'fontsize'
      :Trap 0
          :If 0=⎕NC'APLToHTMLColourScheme'
              APLToHTMLColourScheme←,⊂'blue' 17 19 23 48 56 57 146,(147+⍳5),214,(215+⍳5)
              APLToHTMLColourScheme,←⊂'darkgreen' 1 26
              APLToHTMLColourScheme,←⊂'brown' 6 16 31 32 41 43 46 49,(118+⍳27),(152+⍳34),(186+⍳27),(220+⍳34)
              APLToHTMLColourScheme,←⊂'darkblue' 4 12 13 14
              APLToHTMLColourScheme,←⊂'purple' 37 39
              APLToHTMLColourScheme,←⊂'pink',(50+⍳3)
              APLToHTMLColourScheme,←⊂'red' 9 11 15 34 36 40 42⊣,(186+⍳68)
              APLToHTMLColourScheme,←⊂'gray' 10 35 47 50
          :EndIf
          html←({(+/∨\' '≠⌽⍵)↑¨↓⍵}⍣(1≥|≡APL))APL ⍝ Make VTV if matrix
          html,¨⍨←⎕UCS 13                        ⍝ Mark line beginnings
          types←∊200⌶html                        ⍝ Colour coding
          html←∊html
          spans←1,2≠/types                       ⍝ Colour spans
          html⊂⍨←spans
          types/⍨←spans
          :For colour :In APLToHTMLColourScheme ⍝ 'Colour' CodeElementNumber CodeElementNumber ...
              mask←types∊1↓colour
              (mask/html)←{(''),(HtmlSafeText ⍵),''}¨mask/html
          :EndFor
          html←∊html          ⍝ Encorporate tags
          html⊂⍨←html=⎕UCS 13 ⍝ Restore lines
          html↓¨⍨←1           ⍝ Remove line markers
          html,⍨¨←↓↑('['∘,,∘'] ')¨⍕¨¯1+⍳⍴html ⍝ Prepend blue line numbers
          html←('pre style="font-family:APL385 Unicode',fontsize,'"')Enclose'code'Enclose CRLF,⍨∊,∘CRLF¨html
      :Else
          html←fontsize APLToHTML APL
      :EndTrap
    ∇

    ∇ html←{id}Anchor pars;href;title;target;other;content
    ⍝ Builds an anchor  tag
    ⍝ pars: content {href} {title} {target} {other_attrs}
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      pars←eis pars
      content href title target other←5↑pars,(⍴pars)↓'' '' '' '' ''
      html←('a',(Attrs'id' 'href' 'title' 'target'{(⍵ ine ⍺)⍵}¨id href title target),Attrs other)Enclose content
    ∇

    ∇ html←{n}BRA html
    ⍝ (BReak After) Add n
's after html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html,←n{(⍺×⍴⍵)⍴⍵}'
'
    ∇

    ∇ html←{n}BR html
    ⍝ (BReak) Add n
's before html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html←(n{(⍺×⍴⍵)⍴⍵}'
'),html
    ∇

    ∇ html←{n}SP html
    ⍝ (SPace) Add n non-breaking spaces   before html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html←(n{(⍺×⍴⍵)⍴⍵}' '),html
    ∇

    ∇ html←{n}SPA html
    ⍝ (SPace) Add n non-breaking spaces   after html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html,←n{(⍺×⍴⍵)⍴⍵}' '
    ∇

    ∇ r←{src}JS script
    ⍝ Javascript
    ⍝ script: javascript script if no src or filename if src=1
      :If 0≠⎕NC'src' ⋄ r←('script src=',quote src)Enclose''
      :Else ⋄ r←'script'Enclose script
      :EndIf
    ∇

    ∇ r←{name}DropDown pars;values;display;gv;mask;n
    ⍝ par - items selected_value(s) attributes sort
    ⍝ if items is 2 column matrix, [;1] is values, [;2] is display
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      :If 2=⍴⍴pars ⋄ pars←,⊂pars ⋄ :EndIf
      pars←pars,(⍴pars)↓('Item1' 'Item2')'Item1' '' 0
      :If ~ischar 1⊃pars ⋄ pars[1]←⊂⍕¨1⊃pars ⋄ :EndIf
      :If 1=⍴⍴1⊃pars ⋄ values display←pars[1]
      :Else ⋄ values display←↓[1]1⊃pars
      :EndIf
      mask←values∊⍕¨eis 2⊃pars
      :If 4⊃pars ⋄ gv←⍒mask ⋄ values←values[gv] ⋄ display←display[gv] ⋄ mask←mask[gv] ⋄ :EndIf ⍝ Sort selected items first
      r←'<select ',(3⊃pars),(name ine' id=',n,' name=',n←quote name),'>',CRLF
      r,←enlist(mask{'<option value="',(⍕⍵),'"',(⍺/' selected="selected"'),'>'}¨values),¨display,¨⊂'</option>',CRLF
      r,←'</select>',CRLF
    ∇

    ∇ r←{atts}(method Form)innerhtml
      :If 9=⎕NC'atts' ⋄ atts←'action="',atts.Page,'"' ⍝ MildPage calling
      :ElseIf 0=⎕NC'atts' ⋄ atts←'' ⋄ :EndIf
      atts,←' method="',method,'"'
      atts,←('post'≡#.Strings.lc method)/' enctype="multipart/form-data"'
      r←('form ',atts)Enclose innerhtml
    ∇

    ∇ r←lab Label tag;id
      id←tag GetAttr'id'
      r←tag,⍨('label',id ine' for="',id,'"')Enclose lab
    ∇

    ∇ r←{name}Edit pars;att;size;val
    ⍝ pars: [value] [size] [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      val size att←3↑pars,(⍴pars)↓''⍬''
      r←Tag'input ',att,((~0∊⍴size)/' size="',(⍕size),'"'),' type="text" value="',(⍕val),'"',name
    ∇

    ∇ r←{name}Submit pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'Submit' ''
      r←Tag'input type="submit" ',(2⊃pars),' value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Button pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'Button' ''
      r←Tag'input type="button" ',(2⊃pars),' value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}File pars
    ⍝ pars: [value [size [attributes]]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓''⍬''
      r←Tag'input ',(3⊃pars),' type="file" size="',(⍕2⊃pars),'" value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Hidden pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'' ''
      r←Tag'input ',(2⊃pars),' type="hidden" value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Password pars;att;size;val
    ⍝ pars: [value] [size] [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      val size att←3↑pars,(⍴pars)↓''⍬''
      r←Tag'input ',att,((~0∊⍴size)/' size="',(⍕size),'"'),' type="password" value="',val,'"',name
    ∇

    ∇ r←{name}CheckBox pars
    ⍝ pars: checked [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←eis pars
      pars←pars,(⍴pars)↓0 ''
      r←Tag'input ',(2⊃pars),' type="checkbox"',name,(0≠1⊃pars)/' checked="checked"'
    ∇

    ∇ r←{name}CheckBoxGroup pars;choices;sel
    ⍝ pars: values/texts
      name←{6::'' ⋄ enlist(,∘('="',name,'"'))¨' id' ' name'}⍬
      :If 2=|≡pars ⋄ choices←pars ⋄ sel←⍬
      :Else ⋄ (choices sel)←pars
      :EndIf
      :If 1=⍴⍴choices ⋄ choices←choices,[1.1]choices ⋄ :EndIf
      sel←(⊃⍴choices)↑sel
      r←enlist name∘{BRA(0 Tag'input type="checkbox"',⍺,' value="',(1⊃⍵),'"',(1=3⊃⍵)/' checked="checked"'),(2⊃⍵)}¨↓choices,sel
    ∇

    ∇ r←{name}RadioButton pars
    ⍝ pars: checked [value [attributes]]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←eis pars
      pars←pars,(⍴,pars)↓0 name''
      r←Tag'input ',(3⊃pars),' type="radio" value="',(2⊃pars),'"',name,(0≠1⊃pars)/' checked="checked"'
    ∇

    ∇ r←{name}RadioButtonGroup pars;choices;sel
    ⍝ pars: values/texts [sel]
      name←{6::'' ⋄ enlist(,∘('="',name,'"'))¨' id' ' name'}⍬
      :If 2=|≡pars ⋄ choices←pars ⋄ sel←⍬
      :Else ⋄ (choices sel)←pars
      :EndIf
      :If 1=⍴⍴choices ⋄ choices←choices,[1.1]choices ⋄ :EndIf
      sel←(⊃⍴choices)↑sel
      r←enlist name∘{BRA(0 Tag'input type="radio"',⍺,' value="',(1⊃⍵),'"',(1=3⊃⍵)/' checked="checked"'),(2⊃⍵)}¨↓choices,sel
    ∇

    ∇ r←{id}Table pars;x;data;atts;tdc;tda;thc;tha;hdrrows;z;mask;cellids;n;rowids;rows
    ⍝ pars: data table_atts cell_attribs header_attribs #header_rows cell-ids? row-ids?
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      :If 2=⍴⍴pars ⋄ pars←,⊂pars ⋄ :EndIf ⍝ Matrix of data only
      pars←pars,(⍴,pars)↓(1 1⍴⊂'data')'' '' '' 0 0 0
      data atts tda tha hdrrows cellids rowids←pars
      hdrrows←⍬⍴hdrrows
      data←((rows←×/¯1↓⍴data),¯1↑⍴data)⍴data
      x←⍕¨data
      :If cellids ⍝ do we add cell ids?
          r←(' id="'∘,∘(,∘'">')¨{enlist'rc',¨⍕¨⍵}¨⍳⍴data),¨x,¨⊂'	'
      :Else
          r←'>',¨x,¨⊂'	'
      :EndIf
      :If (⍴tda)≡⍴data
          r←('<td'∘,¨tda ine¨' ',¨tda),¨r
      :ElseIf (⍴tda)=¯1↑⍴data
          r←↑(⊂'<td'∘,¨tda ine¨' ',¨tda),¨¨↓r
      :Else
          r←('<td',tda ine' ',tda)∘,¨r
      :EndIf
      :If z←0≠hdrrows ⋄ r[⍳hdrrows;]←(⊂''),¨x[⍳hdrrows;],¨⊂'	' ⋄ :EndIf
      :If rowids ⍝ if using row ids, add them for non-header rows
          r←(⍳rows){'
',⍵,'
',CRLF}¨,/r
      :Else
          r←'
'∘,¨(,/r),¨⊂'
',CRLF
      :EndIf
      :If z ⋄ mask←(4+⍴r)⍴1 ⋄ mask[n←1,(2 3+hdrrows),⍴mask]←0
          r←mask\r
          r[n]←'
' '
' '
' '
',¨⊂CRLF
      :EndIf
      r←('table',(id ine' id="',id,'"'),(atts ine' ',atts))Enclose CRLF,(enlist r),CRLF
    ∇

    ∇ r←{name}List pars;items;ordered;t
    ⍝ pars: items [ordered? 0/1]
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      :Select ≡pars
      :CaseList 1 ¯2 ⋄ ordered←¯1↑pars ⋄ items←eis ¯1↓pars
      :Case 2 ⋄ ordered←0 ⋄ items←pars
      :Else ⋄ (items ordered)←2↑pars,(⍴,pars)↓'' 0
      :EndSelect
      t←(1+ordered)⊃'ul' 'ol'
      r←(t,name ine' id=',quote name)Enclose enlist'li'∘Enclose¨items
    ∇

    ∇ r←{name}MultiEdit pars
    ⍝ pars: (rows cols) [values [attributes]]
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      pars←pars,(⍴pars)↓(10 40)'' ''
      r←'textarea ',(3⊃pars),(name ine' id="',name,'" name="',name,'"'),' rows="',(⍕1 1⊃pars),'" cols="',(⍕1 2⊃pars),'"'
      r←r Enclose CRLF,(2⊃pars),CRLF
    ∇

      MakeStyle←{
          ⍺←''
          0∊⍴⍵:''
          (' ',⍨¯2↓enlist(eis ⍺),¨⊂', '),Styles ⍵
      }

    ∇ r←HtmlSafeText txt;i;m;u;ucs
    ⍝ make text HTML "safe"
      r←,⎕FMT txt
      i←'&<>"#'⍳r
      i-←(i=1)∧1↓(i=5),0 ⍝ mark & that aren't &#
      m←i∊⍳4
      u←127<ucs←⎕UCS r
      (m/r)←('&' '<' '>' '"')[m/i]
      (u/r)←(~∘' ')¨↓'G<&#ZZZ9;>'⎕FMT u/ucs
      r←enlist r
    ∇

:EndNamespace