intertext
Version:
Services for Recurrent Text-related Tasks
213 lines (195 loc) • 9.91 kB
text/coffeescript
'use strict'
############################################################################################################
CND = require 'cnd'
rpr = CND.rpr
badge = 'INTERTEXT/HTML'
log = CND.get_logger 'plain', badge
info = CND.get_logger 'info', badge
whisper = CND.get_logger 'whisper', badge
alert = CND.get_logger 'alert', badge
debug = CND.get_logger 'debug', badge
warn = CND.get_logger 'warn', badge
help = CND.get_logger 'help', badge
urge = CND.get_logger 'urge', badge
echo = CND.echo.bind CND
{ jr, } = CND
#...........................................................................................................
DATOM = new ( require 'datom' ).Datom { dirty: false, }
{ new_datom
lets
freeze
thaw
is_frozen
select } = DATOM.export()
types = require './types'
{ isa
validate
# cast
# declare
# declare_cast
# check
# sad
# is_sad
# is_happy
type_of } = types
#...........................................................................................................
{ Cupofjoe } = require 'cupofjoe'
assign = Object.assign
excluded_content_parts = [ '', null, undefined, ]
compact_tagname_re = ///
(?<prefix>[^\s.:#]+(?=:)) |
(?<id>(?<=#)[^\s.:#]+) |
(?<class>(?<=\.)[^\s.:#]+) |
(?<name>[^\s.:#]+)
///ug
#-----------------------------------------------------------------------------------------------------------
@parse_compact_tagname = ( compact_tagname, strict = false ) ->
R = {}
for { groups, } from compact_tagname.matchAll compact_tagname_re
for k, v of groups
continue if ( not v? ) or ( v is '' )
if k is 'class'
( R.class ?= [] ).push v
else
if ( target = R[ k ] )?
throw new Error "^intertext/htmlish/parse_compact_tagname@1^ found duplicate values for #{rpr k}: #{rpr target}, #{rpr v}"
R[ k ] = v
if strict and not R.name?
throw new Error "^intertext/htmlish/parse_compact_tagname@2^ illegal compact tag syntax in #{rpr compact_tagname}"
return R
#===========================================================================================================
#
# #-----------------------------------------------------------------------------------------------------------
# @_escape_text = ( x ) ->
# R = x
# R = R.replace /&/g, '&'
# R = R.replace /</g, '<'
# R = R.replace />/g, '>'
# return R
# #-----------------------------------------------------------------------------------------------------------
# @_as_attribute_literal = ( x ) ->
# R = if isa.text x then x else JSON.stringify x
# must_quote = not isa._intertext_html_naked_attribute_text R
# R = @_escape_text R
# R = R.replace /'/g, '''
# R = R.replace /\n/g, ' '
# R = "'" + R + "'" if must_quote
# return R
###
#-----------------------------------------------------------------------------------------------------------
@_parse_compact_tagname = ( compact_tagname ) ->
{ tagname
attributes } = ( compact_tagname.match /^(?<tagname>[^#.]*)(?<attributes>.*)$/ ).groups
R = {}
R.tagname = tagname unless tagname is ''
return R if attributes is ''
for attribute in attributes.split /([#.][^#.]*)/
continue if attribute is ''
avalue = attribute[ 1 .. ]
unless avalue.length > 0
throw new Error "^intertext/_parse_compact_tagname@1234^ illegal compact tag syntax in #{rpr compact_tagname}"
if attribute[ 0 ] is '#' then R.id = avalue
else ( R.class ?= [] ).push avalue
R.class = R.class.join ' ' if R.class?
return R
#-----------------------------------------------------------------------------------------------------------
@tag = ( compact_tagname, attributes, content... ) ->
{ start, content, end, } = @_tag compact_tagname, attributes, content
return [ start, content..., end, ] if end?
return [ start, content..., ]
#-----------------------------------------------------------------------------------------------------------
@_tag = ( compact_tagname, attributes, content, settings ) ->
validate.nonempty_text compact_tagname
{ tagname, id, class: clasz, } = @_parse_compact_tagname compact_tagname
validate.intertext_html_tagname tagname
use_attributes = false
processed_content = []
call_functions = settings?.call_functions ? true
content = thaw content if ( Object.isFrozen content )
#.........................................................................................................
if attributes?
if isa.object attributes then use_attributes = true
else content.unshift attributes
#.........................................................................................................
if content.length is 0
sigil = '^'
end_tag = null
#.........................................................................................................
else
sigil = '<'
end_tag = new_datom ">#{tagname}"
for part in content
continue if part in excluded_content_parts
switch type_of part
when 'text' then processed_content.push new_datom '^text', { text: part, }
when 'function'
if call_functions then processed_content.push x unless ( x = part() ) in excluded_content_parts
else processed_content.push part
else processed_content.push part
#.........................................................................................................
if id? or clasz?
idclass = {}
idclass.id = id if id?
idclass.class = clasz if clasz?
if use_attributes
attributes = assign idclass, attributes
else
use_attributes = true
attributes = idclass
#.........................................................................................................
if use_attributes then start_tag = new_datom "#{sigil}#{tagname}", attributes
else start_tag = new_datom "#{sigil}#{tagname}"
#.........................................................................................................
processed_content = processed_content.flat Infinity
return { start: start_tag, content: processed_content, end: end_tag, } if end_tag?
return { start: start_tag, content: processed_content, }
###
#-----------------------------------------------------------------------------------------------------------
@html_from_datoms = ( ds, settings ) -> return ( @_html_from_datom d for d in ds.flat Infinity ).join ''
#-----------------------------------------------------------------------------------------------------------
@$html_from_datoms = ->
{ $, } = ( require 'steampipes' ).export()
return $ ( d, send ) =>
return send @_html_from_datom d unless isa.list d
send x for x in @html_from_datoms d...
return null
#-----------------------------------------------------------------------------------------------------------
@_html_from_datom = ( d ) ->
return @_html_from_datom ( @text d )[ 0 ] if isa.text d ### TAINT ??? ###
DATOM.types.validate.datom_datom d
atxt = ''
sigil = d.$key[ 0 ]
tagname = d.$key[ 1 .. ]
is_empty_tag = isa._intertext_html_empty_element_tagname tagname
x_key = null
is_block_tag = d.$blk ? false
bnl = if is_block_tag then '\n\n' else '' ### TAINT make configurable ###
xnl = '\n' ### TAINT make configurable ###
#.........................................................................................................
### TAINT simplistic solution; namespace might already be taken? ###
if sigil in '[~]'
switch sigil
when '[' then sigil = '<'
when '~' then sigil = '^'
when ']' then sigil = '>'
[ x_key, tagname, ] = [ tagname, 'x-sys', ]
#.........................................................................................................
return ( @_escape_text d.text ? '' ) if ( sigil is '^' ) and ( tagname is 'text' )
return ( d.text ? '' ) if ( sigil is '^' ) and ( tagname is 'raw' )
return "<!DOCTYPE #{d.$value ? 'html'}>#{xnl}" if ( sigil is '^' ) and ( tagname is 'doctype' )
return "</#{tagname}>#{bnl}" if sigil is '>'
#.........................................................................................................
### NOTE sorting atxt by keys to make result predictable: ###
if isa.object d.$value then src = d.$value
else src = d
atxt += " x-key=#{@_as_attribute_literal x_key}" if x_key?
for key in ( Object.keys src ).sort()
continue if key.startsWith '$'
if ( value = src[ key ] ) is true then atxt += " #{key}"
else atxt += " #{key}=#{@_as_attribute_literal value}"
#.........................................................................................................
### TAINT make self-closing elements configurable, depend on HTML5 type ###
slash = if ( sigil is '<' ) or is_empty_tag then '' else "</#{tagname}>#{bnl}"
x_sys_key = if x_key? then "<x-sys-key>#{x_key}</x-sys-key>" else ''
return "<#{tagname}>#{slash}#{x_sys_key}" if atxt is ''
return "<#{tagname}#{atxt}>#{x_sys_key}#{slash}"