Hashtag Markup

Introduction

This document defines and explains the syntax of the Hashtag Markup Language. For more information about processing Hashtag Markup using the Hashtag Framework, see the Setup Guide, or the Development Guide.

L ive examples are linked throughout this document to the: ive examples are linked throughout this document to the: B uild A cceptance T est

Conventions

These conventions will be used when describing Hashtag Markup syntax.

Bold ⇒ Markup keyword — Literal value Italic ⇒ Variable name — Replaced with arbitrary value ( A | B ) ⇒ Mandatory Choice, options delimited with | [ ] ⇒ Optional / Optional Choice … ⇒ Optionally repeat

These terms will be used to describe Hashtag Markup.

Tag ⇒ Markup that begins with <# and ends with #> Command ⇒ Tag content that begins with a command keyword Block ⇒ Tag containing multiple Commands, delimited with ; Directive ⇒ Command that extends a previous Command in the same Block Template ⇒ Markup that is processed conditionally or repetitively

Comments allow for private notes to be added to Hashtag Markup. Comments are stripped from Markup by the Hashtag Framework, and never delivered to the end-user. Single-line comments begin with // wherever a Command could start, ending with any vertical-whitespace character. Multi-line comments begin with <#: and end with :#>.

Example Single-Line Comments:

<# // single-line comment

// single-line comment

Command ; // single-line comment

#>

Example Multi-Line Comments:

<#: multi-line comment

multi-line comment

multi-line comment :#>

Variables

Hashtag Variables store a value that can later be referenced by name to use the stored value. Variables are not assigned a type, such as number or text, rather Contexts can be applied when storing or using the values. Variable values can be injected into Markup using Variable Tags. Variables are either Global, or Local to a Template.

Syntax for Global Variables:

<#[ variable_name ]#>

Syntax for Local Template Variables:

<# variable_name #>

Buckets

Hashtag Buckets are collections of variables using the same delimited prefix. Variable Buckets are period . delimited, for example: user.name and user.address are both in the user Bucket. Buckets can be sent as collections of values to external APIs. The print bucket Command creates a nested HTML representation of all Variable names and values in a Bucket, useful for development testing. The empty bucket Command deletes all Variables in a Bucket. Bucket Loop Templates enable processing every Variable value in a given Bucket.

Reserved Buckets

The following Buckets are reserved by the Hashtag Framework: System, Config, Request, URL, URI, GET, POST, Form, Session, Cookie, and Cache.

Session, Cookie, and Cache are special Buckets that store Variable values using different methods. The Session bucket uses the configured PHP Session Handler, generally set to use an in-memory cache. Setting a value in the Session bucket will make it available in any future requests made by the same user. The Cookie bucket is populated with HTTP Cookie values sent with the request. Setting a value in the Cookie bucket will include the new value in the response, instructing the browser to send it back with any future requests. The Cache bucket uses the configured key→value store, such as Memcached. Setting a value in the Cache bucket will make it available in any future requests made by any user, but values may be deleted at any time to make room for new values.

The Config bucket is populated with the current Hashtag Framework Configuration. To learn more, read the Configuration Guide

System Bucket

The Hashtag Framework provides the following values in the reserved System Bucket:

Name Description date Sortable Date — YYYY/MM/DD date_time Date with 12h Time with Timezone date_time_short Sortable Date with 24h Time — YYYY/MM/DD HH:MM:SS timestamp Whole seconds since Unix Epoch timestamp_float

timestamp_long Seconds since Unix Epoch with decimal precision time 12H Time with Timezone time_short 24H Time month 2 digit Month number — Leading 0 for 1-9 month_short 1-2 digit Month number month_name Month Name month_name_short 3 Letter Month Name day 2 digit Day of month number — Leading 0 for 1-9 day_short 1-2 digit Day of month number day_name Day of the week — Monday, Tuesday... day_name_short 3 Letter Day of the week — Mon, Tue... year Current Year domain Current Domain page Current Page Name request Request URI host Host name — Domain with optional Port host_url URL of the Host secure_host_url Secure URL of the Host session_id Session ID for current user referrer Referring URL — Ensures a trailing ? referring_host Referring Host referring_host_url Referring Host URL secure_referring_host_url Secure URL for the Referring Host secure_referring_page_url Secure URL for the Referring Page referring_scheme Protocol Scheme from the Referring URL referring_query Query string from the Referring URL remote_addr Remote End-User IP Address

Request Bucket

The Hashtag Framework automatically adds all HTTP POST and GET values to the reserved Request Bucket. HTTP POST values are also added to the Post Bucket. HTTP GET values are also set in the URL, URI, and Get Bucket.

Applying Contexts

Hashtag Contexts can control how values are interpreted or formatted. For example, encoding user provided text for inclusion in a HTML page, or adjusting a timestamp value according to the end user timezone. Contexts can be applied to variables directly, or to string values in many Commands. Contexts are case-insensitive, and can be chained.

Syntax:

( variable_name | "value" ) [ as context ]…

Values for context:

Context Description Text Contexts lower

lowercase Converts all characters to lowercase upper

uppercase Converts all characters to UPPERCASE trim

trimmed Strips all whitespace characters from both ends name

label

word Strips all non-alphanumeric characters except _ and - HTML

web

websafe Converts HTML control characters into their HTML encodings, and converts new-line characters into HTML <br> tags. All user provided data that is intended to be displayed as-is in an HTML page should use this context to prevent HTML/CSS/JavaScript injection. pre Similar to HTML context, but new-line characters are not converted to HTML <br> tags csv Replaces " characters with "" so the value is safe to inject in a "double-quoted CSV value" URL

URI Convert URL control characters into their URL encodings. Any variable injected into a URL query param that may contain URL control characters should use this context to avoid URL corruption. Numeric Contexts number

numeric

decimal Strips everything except 0-9, the negation sign (-), and the decimal separator (.) integer

int Converts to numeric then strips any decimal value. default is 0. multiple "number" Converts to numeric then multiplies by number. hex Converts numeric to hexadecimal value. default is 0. abs Converts to numeric then strips any negation. default is 0. pluralized "format string" Converts value to numeric, then uses the | character to split format string. If the value is 1, the first half of the split format string is appended to the value after a space; Otherwise, the 2nd half of the format string is appended to the value after a space. negativeswitch "format string" Converts value to numeric, then uses the | character to split format string. If the value is negative, the first half of the split format string is returned; Otherwise, the 2nd half of the format string is returned. changepercent Converts value to numeric with 2 decimal places, then appends % after a space, and prepends either + or - before a space. currency Strips all non-numeric characters, rounds to 2 decimals, and formats as X,XXX.XX altcurrency Strips all non-numeric characters, rounds to 2 decimals, and formats as X.XXX,XX dollar

dollars

USD Strips all non-numeric characters, rounds to 2 decimals, and formats as $ X,XXX.XX euro

euros

EUR Strips all non-numeric characters, rounds to 2 decimals, and formats as € X.XXX,XX Date & Time Contexts timespan

span

seconds Converts a value in the format h:mm:ss to total number of seconds minutes Converts a value in the format h:mm:ss to total number of minutes hours Converts a value in the format h:mm:ss to total number of hours day Converts the value into a timestamp, then returns the integer day of month for the date month Converts the value into a timestamp, then returns the integer month of year for the date year Converts the value into a timestamp, then returns the year for the date timespan "format string" Replacement tokens for format string:

d = days

hh = 2 digit hours

h = 1-2 digit hours

mm = 2 digit minutes

m = 1-2 digit minutes

ss = 2 digit seconds

s = 1-2 digit seconds Timezone Adjustments tz

timezone The "session.timezone" or "session.tz" value will be applied, and the datetime or timestamp string will be converted and returned in its original format tz "timezone"

timezone "timezone" timezone value will be applied and the datetime or timestamp string will be converted and returned in its original format tz using variable

timezone using variable variable will be applied and the datetime or timestamp string will be converted and returned in its original format Hash Algorithms hash [ salted by "string" | salted by variable | using "algorithm" ]… Value is hashed using an optional salt provided as a "string" or variable value. The default hash algorithm is sha256. Hashtag supports all of the hash algorithms supported by PHP. Form Contexts hidden_html_form_inputs

hiddenhtmlforminputs Parses the value as a URL, extracts any GET variables and converts them to hidden HTML form input elements. querylink Parses the value as a URL, strips any duplicate URL query params, and ensures the URL has a "?" bucket Splits the value on . characters, trims whitespace, and returns the first half of the split value. key Splits the value on . characters, trims whitespace, and returns the second half of the split value.

Set Command

The set Command stores a value for a Variable. Contexts can be applied to the end of the command. The named variable can include Bucket names nested with . period characters. The system Bucket is reserved, and can not be used for storing values using the set Command.

Syntax for Text Strings:

set variable to "value" [ as context ]… ;

Syntax for Mutli-Line Text:

set variable to """

multi-line

value

""" [ as context ]… ;

Syntax for Math Expressions:

set variable to math_expression [ as context ]… ;

The math_expression can include Variable Tags, but after those values are injected, the resulting expression can only contain whitespace and these characters: 0123456789.+-*/()^!%.

Syntax for New UUID:

set variable to new UUID;

The set Command has built in support to generate a new UUID value and store it as a variable.

Syntax for Random Number:

set variable to random [ cents ] from number to number ;

When using random cents, the generated random number will have 2 digit decimal precision. An alternate syntax using the .. operator is also supported.

set variable to random [ cents ] number .. number ;

Syntax for Random Record:

set variable to random table.field ;

All of the logical_comparison filters supported by include when Directives can be used to filter random records.

set variable to random table.field [ logical_comparison ]… ;

Syntax for Count of Records:

set variable to count of table.field ;

All of the logical_comparison filters supported by include when Directives can be used to filter counted records.

set variable to count of table.field [ logical_comparison ]… ;

Syntax for Google Drive Folder ID:

set variable to [ public | private ] google drive folder id by name "folder_name";

The set Command has built in support to lookup a Google Drive Folder ID by name. If the folder doesn’t exist, it will be created automatically, and the ID for the new Folder will be set for the variable. When a new Google Drive Folder is created, the public or private keyword is used to set permissions.

Round Command

The round Command is used to round a value to a specified number of decimal places.

Syntax:

round variable to number decimals;

Replace Command

The replace Command is to used to alter a value, finding and replacing a string or pattern with another string.

Syntax for Text String Replacement:

replace [ i ] "find" in variable with "replace" ;

Syntax for Regular Expression Replacement:

replace [ i ] pattern "pattern" in variable with "replace" ;

The optional i keyword enables case insensitive matching. The more readable case insensitive is also supported, as well as simply insensitive or ci. The pattern keyword for Regular Expression matching can be replaced with re, regex, or regular expression.

Append Command

The append Command is to used to alter a value by appending with another string. An optional using value can be provided to join non-blank values, such as when creating a comma separated list.

Syntax:

append variable with "string" [ using "joiner" ] ;

Increment Command

The increment Command is to used to alter a numeric value by adding another value.

Syntax:

increment variable by number ;

Decrement Command

The decrement Command is to used to alter a numeric value by subtracting another value.

Syntax:

decrement variable by number ;

The print Command allows for outputting a string of text, that may include Variable Tags or applied Contexts.

Syntax:

print "text" [ as context ]… [ if set else "text" [ as context ]… ] ;

The optional if set else conditional can be used when the original text contains only Variable Tags, and alternate text should be printed when the original text results in only white-space after Variable Tag values are injected.

Redirect Command

The redirect Command is used to redirect the request to another page URL.

Syntax:

redirect to "URL" ;

Note: The semi-colon ; is optional when a Tag contains only a single redirect Command.

Conditionals — If, Else

Conditionals are used to limit execution based on logical expressions. Hashtag Markup supports multiple forms of Conditionals: Templates and { Blocks }.

Syntax for Conditional Templates:

<# if logical_expression #>

Template

[<# else if logical_expression #>

Template ]…

[<# else #>

Template ]

<# end if #>

A logical_expression is a comparison of "double-quoted" values that may include Variable Tags. Comparison operators include: is, =, ==, ===, is not, !=, !==, <>, >, >=, <, <=. ( Paren ) nesting is also supported, with logical operators: and, &&, or, ||, xor, not, !.

Syntax for Conditional { Blocks }:

<# if ( logical_expression ) {

( Command(s); Template | Template )

[ } else if ( logical_expression ) {

( Command(s); Template | Template ) ]…

[ } else {

( Command(s); Template | Template ) ]

} #>

Conditional { Blocks } can simplify Markup in many cases, however, the Hashtag Framework may misinterpret JavaScript } else { structures in the Template in some cases. Conditional Templates should be used in most cases.

Bucket Loops

Hashtag Loops are used to process all Variables in a Bucket. Loops can be nested by increasing the number of leading # in the start and end Tags.

Syntax:

<# start loop for ( bucket | <# local_bucket #> ) ;

[ loop_directive; ]…

#>

Template

<# end loop #>

Syntax for loop_directive:

save ( key | value ) to local_variable ;

When a local_variable is not provided to save the key or value, then the local variables <# key #> and <# value #> will be set.

Range Loops

Hashtag Range Loops are used to repeatedly process a template while incrementing a counter. Loops can be nested by increasing the number of leading # in the start and end Tags.

Syntax:

<# start loop from variable_name to variable_name ;

[ loop_directive; ]…

#>

Template

<# end loop #>

Alternate Syntax:

<# start loop math_expression .. math_expression ;

[ loop_directive; ]…

#>

Template

<# end loop #>

The math_expression can include Variable Tags, but after those values are injected, the resulting expression can only contain whitespace and these characters: 0123456789.+-*/()^!%.

Syntax for loop_directive:

save value to local_variable ;

When a local_variable is not provided to save the loop counter value, the local variable <# value #> will be set.

Loop Until Conditional

Hashtag Loops Until are used to repeatedly process a Template Until a Conditional is met. Loops can be nested by increasing the leading number of # in the start and end Tags.

Syntax:

<# start loop until variable is ( number | variable ) ;

[ loop_directive; ]…

#>

Template

<# end loop #>

Alternate Syntax:

<# start loop until logical_expression ;

[ loop_directive; ]…

#>

Template

<# end loop #>

A logical_expression is a comparison of "double-quoted" values that may include Variable Tags. Comparison operators include: is, =, ==, ===, is not, !=, !==, <>, >, >=, <, <=. ( Paren ) nesting is also supported, with logical operators: and, &&, or, ||, xor, not, !.

Syntax for loop_directive:

save to variable ;

Lists

Hashtag Lists allow for sets of data records to be retrieved and processed. Multiple back-ends are supported for listed records, including Google Sheets, SQL Databases, and Google Drive Folders.

Syntax:

<# start list — ;

[ list_directive; ]…

#>

[ <# start list_template #>

Template

<# end list_template #> ]…

<# end list #>

Values for list_template:

header ⇒ Template processed first, only when rows exist row ⇒ Template processed for each row in the list footer ⇒ Template processed last, only when rows exist no results ⇒ Template processed only when no rows exist

The start list Block defines the source of the data records, as well as any Directives that further sort, filter, process, or paginate the records.

Syntax for SQL Database:

<# start list for table_name ;

[ list_directive; ]…

#>

The table_name value is stripped of non-word characters, and converted to lowercase before it is used as an SQL Table name. List values will be named according to the column names from the SQL Table.

Syntax for Google Sheet:

<# start list for google sheet ( sheet_id | "sheet_name" ) ;

[ list_directive; ]…

#>

A Google Sheet List will default to the first worksheet if a worksheet name is not selected. Use the include from directive to select a worksheet by name. The sheet_id or sheet_name can include Variable Tags. Local Template variables will be set according the header values for each column in the 1st row.

Syntax for Google Drive Folder:

<# start list for google drive folder folder_id ;

[ list_directive; ]…

#>

Google Drive Folder List values include: id, uuid, title, name, description, filename, extension, size, type, kind, icon_url, drive_url, thumbnail_url, web_url, download_url, drive_download_url, alternate_url, image_height, image_width, created_on, updated_on, last_modified_by_username.

List Directives

The start list Block can include multiple Directives to further relate, filter, process, sort, or paginate the data. Some Directives have different syntax or capabilities based on the source of the data, such as the differences between SQL Databases and Google Sheets.

Include When — Filter Records

The include when Directive uses a logical_expression similar to Conditionals, but further allows the first operand in comparison expressions to be a field name instead of a "quoted value".

Syntax:

include when logical_expression;

SQL Database Lists support these additional regular expression comparison operators in logical_expression: ~, REGEXP, RLIKE. SQL Database Lists also support the additional comparison operator: in, that compares field values against a quoted comma-separated list of values. SQL Database Lists also support the additional comparison operator: contains, that matches field values containing the quoted value. When the second operand of a comparison expression is a "quoted value" immediately follwed by if set, the expression will be ignored if injecting Variable Tag values results in only whitespace.

Syntax for Google Sheet:

include [ all columns | csv_column_names ]

[ from "worksheet_name" ]

[ when logical_expression ] ;

Google Sheet Lists support choosing a specific Worksheet with the include from Directive.

Sort Order

The sort by Directive allows for records to be ordered before they are processed. Alternatively, the order by Directive is works exactly the same.

Syntax:

( sort | order ) by property [ , property ]… ;

Syntax for property:

field [ as type ] [ asc | desc ]

When a type isn't provided, alphabetic ordering is used. When asc or desc is not provided, sorting will be done in ascending order. Instead of asc or desc, a more readable in ascending order or in descending order can be used.

Values for type:

numeric ⇒ Uses numeric ordering dollars ⇒ Strips $ characters, then uses numeric ordering upper ⇒ Case insensitive alphabetic ordering

Relate Records

The relate Directive allows related records to be joined together and included as List rows.

Syntax:

[ must ] relate field to field ;

When the must keyword is used, only items that have related records will be included.

When relating multiple SQL Database Tables, the show unique Directive will place a limit of one row per record from table_name, regardless of how many related records were matched. There is also support for ( paren ) grouping with AND / OR conditionals.

Syntax for Relate All:

relate all table_name ;

Syntax for Show Unique:

show unique table_name ;

Exclude Columns

The include columns and exclude columns Directives limit the amount of data that is queried and returned from the database. By default, all field values are queried and made available as Local Template Variables.

Syntax for Include Columns:

include columns column_name [ , column_name ]… ;

Syntax for Exclude Columns:

exclude columns column_name [ , column_name ]… ;

Set to Total of

The set to total of Directive allows a Local Template Variable to be incremented by a field value for each row.

Syntax:

set local_variable to total of field ;

The set to total of Command can also be used in the Footer Template, however field is replaced with a Local <# variable #> Tag.

Pager — Rows Per Page

The show number rows per page Directive limits the number of records processed for the List. The Hashtag Framework creates an HTML pager with links to navigate through the pages of records.

Syntax:

show number rows per page;

Syntax for SQL Database:

show number ( rows | table_name ) per page;

A table_name is provided instead of rows when multiple SQL Tables are related together.

Link Pager Anchor

The link pager Directive can be used to append a string value to pager URLs.

Syntax:

link pager to #fragment ;

Hide Pager

When a rows per page Directive is used, the Hashtag Framework creates an HTML pager. By default, the pager is included before and after the List. When there is only a single page of records, the pager is not included. The show and hide Directives can be used to only include the HTML pager before or after the list, or not at all.

Syntax:

( show | hide ) ( both pagers | top pager | bottom pager ) ;

Limit Rows

The limit Directive can be used to set a maximum number of records to process.

Syntax:

limit number rows ;

Cache List

The cache Directive can be used to store the results of the List for all pages. A Cached List can significantly improve performance for high traffic pages, or for data that doesn't change often.

Syntax:

cache as Key ;

Save to Variable

The save to Directive can be used to store the List result content in a Hashtag Variable instead of including it in the page.

Syntax:

save to variable_name ;

List Template Variables

List Templates provide additional Local Variables to track the total number of rows, and the current row number.

Local Template Variables:

<# numberofrows #> ⇒ Total number of rows in the list <# rownumber #> ⇒ Current row number starting with 1 <# lastrow #> ⇒ returns "yes" when the current row is the last row, blank otherwise

Row Template Commands

Additional Commands are available in the List row Template.

The apply row Command will save all of the Local Row Bucket values to a Global Bucket that can be used after the List. When a bucket_name is not provided, the SQL Table name will be used.

Syntax for Apply Row:

apply row [ as " bucket_name " ] ;

The row loop Command creates a Template that is continually processed for rows that match a given field value. This is used when tables are related together, allowing all related records to be processed for the current row.

Syntax for Row Loop:

<# start row loop for field; #>

Template

<# end row loop #>

The break Command provides controls for List processing. This can be used within a row loop to jump to the next row or the next loop.

Syntax for Break:

<# break to next row; #>



Forms

Hashtag Forms provide handlers to process and store submitted data. Multiple back-ends are supported for storing records, including Google Sheets and SQL Databases.

Syntax:

<# start form — ;

[ form_directive; ]…

#>

Form Template

<# end form #>

The start form Block can define a data back-end for storing records. An optional record_id can be set to pre-populate the form with data from an existing record. Form Directives can be used to set attributes for the HTML <form> tag, or to define Form Action Commands. The Form Template supports all HTML 5 compliant form elements. Tags can be added as attributes to form input elements, this names their field value, and includes it with submitted data.

Syntax for SQL Database:

<# start form for table_name [ record_id ] ;

[ form_directive; ]…

#>

Syntax for Google Sheet:

<# start form for google sheet ( "sheet_name" | sheet_id )

[ "worksheet_name" ] [ row_id ] ;

[ form_directive; ]…

#>

Action Only Forms do not automatically store records of submitted data, but Form Actions can still be used to create or update existing records when the form is processed.

Syntax for Action Only Forms:

<# start form;

[ form_directive; ]…

#>

Form Directives

The start form Block can include multiple Directives that further control how the form is styled and validated, and how submitted data is handled.

The form Bucket is used to set attributes for the HTML <form> tag, such as class.

Syntax for Set HTML Attribute:

set form. attribute_name to "value" ;

Restrict Posts

The restrict posts Directive requires form submissions to include a security code, such as a password or CAPTCHA. The restricted Form Action can be used to redirect failed submissions to a different URL.

Syntax for Restrict Posts:

restrict posts to "value" in form_field ;

[ when restricted redirect to "URL" ; ]

Syntax for Google reCAPTCHA:

restrict posts using google recaptcha;

[ when restricted redirect to "URL" ; ]

The form body must include a <# google recaptcha #> tag, which will be replaced with an HTML div tag.

Form Actions

The when form_action Directive sets Commands to be processed when specific Form Actions are triggered. Multiple Directives can be set for each Form Action. Any call JavaScript Command will be executed before the HTML Form is submitted. If the JavaScript evaluates as false the HTML Form will not be submitted.

Syntax for Conditional Form Actions:

when form_action [ and ( logical_expression ) ]

( Command | call JavaScript ) ;

Values for form_action:

creating ⇒ Processed when a new record is created updating ⇒ Processed when an existing record is updated deleting ⇒ Processed when an existing record is deleted processing ⇒ Processed for Action Only Form submissions done ⇒ Processed after all other Form Actions are processed

When Form Action Commands are processed, <# form. field #> Local Variable Tags will inject field values submitted with the form. The <# form.id #> Local Variable Tag can always be used to reference the stored record ID. When existing records are being updated or deleted, Local Variables will be set for each field in the record.

Custom Form Actions are named with <# form_action button #> attributes, added to any HTML button in the Form Template. The default Form Actions are triggered by buttons with these attributes: <# create button #>, <# update button #>, or <# delete button #>. When a form is pre-populated with data from an existing record, the create button will not be included, otherwise the update and delete buttons will not be included. Action Only Forms can use a <# process button #> attribute to trigger the processing Form Action.

Form Template

Hashtag Form Templates support all HTML Form Elements, including the latest HTML 5 Validated Input Types. The following HTML Form Elements can be Tagged as a field by adding a <# field_name #> attribute: input, textarea, select, and button.

A preset value for a field input element can be set using the value attribute, however, all preset values are overwritten when an existing record is loaded. Textarea elements are also overwritten when an existing record record is loaded, and any select elements will have the selected attribute set for the option element that matches the existing record.

For radio input types, the field Tag must be added as an attribute for each radio input choice.

Form Action Button Tags can be added as attributes to any submit or button input types, or <button> tags.

Form Templates are processed as Hashtag Markup before they are parsed for field tags.

Input Validation

Form element attributes, such as required, are validated using added JavaScript when HTML 5 is not supported. Inputs are further validated by the Hashtag Framework Form Handler when data is submitted, but before any records are stored or Form Actions are processed. When submitted data fails validation, the request is redirected back to the Form with errors indicated. The formnovalidate attribute can be added to any button to disable validation.

These HTML Form Input types are validated: email, integer, number, url, date, and range. The Hashtag Framework further supports the decimal, dollars, USD, and datetime Input types.

These HTML Form element attributes are validated: required, pattern, min, max, and step. The Hashtag Framework further supports the unique and case-insensitive-unique attributes, ensuring the field value is unique over all records stored in the same SQL Database Table.

File Uploads

To save uploaded file information in stored records, any <input type="file"> tags in the Form Template can include a field Tag attribute. The Hashtag Framework supports multiple back-ends for storing uploaded files, and can be configured for any local filesystem, Google Cloud Storage, or Amazon S3. A separate back-end can be configured to store private files, which are not directly web accessible. If a back-end was not configured to store uploaded files, PHP may still store the files in a temporary directory, and they will be kept there.

Syntax for File Input Attribute:

<# [ private ] field #>

The following field values are added to records with File Uploads:

field ⇒ Filename of the uploaded file field _type ⇒ The mime type provided with the uploaded file field _path ⇒ The filepath for the stored file field _web_url ⇒ Public URL for the stored file, unless private keyword set field _image_width ⇒ Only set for image file uploads field _image_height ⇒ Only set for image file uploads

If the Google API has been configured, the following attribute can be used to upload files to Google Drive, regardless of the back-end configured to store file uploads.

Syntax for Google Drive File Input Attribute:

<# upload to google drive "folder_id" for field #>

The following additional field values are added to records with File Uploads to Google Drive:

field _drive_url ⇒ Private URL to edit the file on Google Drive field _drive_web_url ⇒ Public URL to download the file from Google Drive field _drive_thumbnail_url ⇒ Public URL for thumbnail image on Google Drive

Checklist Forms

Checklist Forms are a hybrid of a List and a Form. Standard List Templates are supported to display the included records. Form Templates are extended to support checkbox inputs that allow individual records to be selected.

Syntax:

<# start checklist form — ;

[ list_directive; | form_directive; | checklist_form_directive; ]…

#>

[ <# start list_template #>

Checklist Form Template

<# end list_template #> ]…

<# end checklist form #>



The start checklist form Block defines the source of the data records, as well as List Directives that further sort, filter, process, or paginate the records. Form Directives are also supported to set HTML <form> tag attributes, or set commands for Form Actions.

Syntax for SQL Database:

<# start checklist form for table_name ;

[ list_directive; | form_directive; | checklist_form_directive; ]…

#>

Checklist Form Actions

The when form_action Directive is extended to support the additional states: checked, and unchecked.

checklist_form_directive: Syntax for

when form_action and [ checked | unchecked ]

( Command | call JavaScript ) ;

Checklist Form Templates

The <# checklist item #> Tag can be added as an attribute to any HTML <input type="checkbox"> tag in the row Template. When the checklist is processed, the checked or unchecked Form Actions will be triggered based on this checkbox state.

JSON-RPC Forms & Workers

Hashtag JSON-RPC Forms provide simple methods to create HTML form interfaces powered by JavaScript to send and receive data. JSON-RPC Workers handle requests by processing a Template for the requested action, then return either a result or error.

Syntax for JSON-RPC Form:

<# start json-rpc form using URL ;

[ form_directive; ]…

#>

Form Template

[ <# start button_action state #>

JavaScript

<# end button_action state #> ]…

<# end json-rpc form #>

Values for state:

request ⇒ JavaScript processed when a request is sent response ⇒ JavaScript processed when a response is received result ⇒ JavaScript processed when the response includes a result error ⇒ JavaScript processed when the response includes a error

For each state, when JavaScript is processed, an object variable with the same name as the state is provided to hold relevant values.

Syntax for JSON-RPC Worker:

<# start json-rpc worker;

[ worker_directive; ]…

#>

[ <# start button_action #>

Template

<# end button_action #> ]…

<# end json-rpc worker #>

Data provided with the request is made available in the params Bucket. To return data with the response, the button_action Templates use set Commands to store values in the result or error Bucket. When values have been set in the error Bucket, the result Bucket values will not be included in the response, as JSON-RPC does not allow responses with both result and error.

Record Commands

The following Commands are available to manipulate stored records: apply, create, update, clone, and delete. Multiple back-ends are supported for storing records, such as Google Sheets and SQL Databases.

Apply Record

The apply Command loads a record, then stores all field values as Bucket Variables.

Syntax for SQL Database:

apply table_name [ . record_id ] [ as "alias" ] [ or redirect to "URL" ] ;

If a record_id is not provided provided, the earliest created record for the Table will be applied. If an alias is not provided, the record will be applied as the table_name.

If the record does not exist, an optional redirect URL can be provided.

Syntax for Google Sheet:

apply google sheet ( "sheet_name" | sheet_id )

[ "worksheet_name" ] row_id [ as "alias" ] ;

Create Record

The create record Command allows new records to be created. Multiple back-ends are supported, including Google Sheets and SQL Databases.

Syntax for SQL Database:

create new record for "table_name" [ as "alias" ] ;

[ create_record_directive; ]…

Syntax for Google Sheet:

create new record for google sheet ( "sheet_name" | sheet_id )

[ "worksheet_name" ] [ as "alias" ] ;

[ create_record_directive; ]…

The update record Command allows for existing records to be updated with new field values. The record is referenced by ID. Each record in a SQL Database table has a column named uuid that is aliased as id. Each row in a Google Sheet has a column with a header row value of Row ID that defaults to column T, but can be located in any column.

Syntax for SQL Database:

update record for "table_name . record_id" [ as "alias" ] ;

[ update_record_directive; ]…

Syntax for Google Sheet:

update record for google sheet ( "sheet_name" | sheet_id )

[ "worksheet_name" ] row_id [ as "alias" ] ;

[ update_record_directive; ]…

Clone Record

The clone Command allows for existing records to be duplicated, and assigned a new ID. When an alias is provided, the new record values are stored in the named Bucket.

Syntax for SQL Database:

clone table_name . record_id [ as "alias" ] ;

Delete Record

The delete record Command allows for an existing record to be deleted.

Syntax for SQL Database:

delete record for "table_name . record_id" ;

Syntax for Google Sheet:

delete record for google sheet ( "sheet_name" | sheet_id )

[ "worksheet_name" ] row_id ;

Delete Record List

The delete record list Command allows for multiple existing records to be deleted based on further Directives.

Syntax for SQL Database:

delete record list for table_name ;

Delete Record List Directives

The delete record list Block can include multiple Directives to further relate, filter, process, sort, or paginate the data. Some Directives have different syntax or capabilities based on the source of the data, such as the differences between SQL Databases and Google Sheets.

Delete When — Filter Records

The delete when Directive uses a logical_expression similar to Conditionals, but further allows the first operand in comparison expressions to be a field name instead of a "quoted value".

Syntax:

delete when logical_expression;

SQL Database Lists support these additional regular expression comparison operators in logical_expression: ~, REGEXP, RLIKE. SQL Database Lists also support the additional comparison operator: in, that compares field values against a quoted comma-separated list of values. SQL Database Lists also support the additional comparison operator: contains, that matches field values containing the quoted value. When the second operand of a comparison expression is a "quoted value" immediately follwed by if set, the expression will be ignored if injecting Variable Tag values results in only whitespace. An alternate keyword syntax using filter instead of delete when is also supported.

Relate Records

The relate Directive allows related records to be joined together to create a list of records to delete.

Syntax:

[ must ] relate field to field ;

When the must keyword is used, only items that have related records will be included.

When relating multiple SQL Database Tables, the show unique Directive will place a limit of one row per record from table_name, regardless of how many related records were matched. There is also support for ( paren ) grouping with AND / OR conditionals.

Syntax for Relate All:

relate all table_name ;

Delete All Records

The delete all records Command allows for completely removing all Records, including their back-end storage container.

Syntax for SQL Database:

delete all records for " table_name " ;

Import Google Sheet

The import Command is used for importing a Google Sheet into an SQL Database.

Syntax:

import google sheet ( "sheet_name" | sheet_id )

[ "worksheet_name" ] into sql_table_name ;

[ import_directive; ]…

When no import_directive is provided, the first row will be assumed to be the column name header, fields will be automatically mapped to SQL Table columns with the same name as the header. Existing records are deleted unless the preserve existing records Directive is given.

Syntax for import_directive to preserve existing data:

preserve existing records;

Syntax for import_directive to treat Row 1 as data:

no header row;

Syntax for import_directive to explicitly map fields:

map ( "sheet_column_name" | sheet_column_letter ) to sql_column [as key] ;

When the as key keyword is used, the named field will be used as the primary key, and its value will serve as the record id value.

Export into Google Sheet

The export Command is used for exporting an SQL Database Table into a Google Sheet.

Syntax:

export sql_table_name into google sheet

( "sheet_name" | sheet_id ) [ "worksheet_name" ] ;

Include Command

The include Command allows Hashtag Markup to be pulled in from another source, and then processed.

Syntax for Include File:

include "filepath";

Syntax for Include Variable:

include variable ;

Include Google Doc

Google Doc content can be pulled in using an include Command, and optionally processed as Hashtag Markup.

Syntax:

include [ processed | raw | and ]… google doc ( "doc_name" | doc_id ) ;

If the processed keyword is omitted, the Google Doc content will not be processed as Hashtag Markup. If the raw keyword is set, the Google Doc will be returned exactly as Google provides it; otherwise, it will be stripped of its HTML container, the CSS will be localized, and any redirected links will be converted to direct links.

Note: The semi-colon ; is optional when a Tag contains only a single include Command.

Restricting Access

The restrict access Command checks for a security group membership in the current session, and redirects to an authentication page if access was not granted.

Syntax:

restrict access to security_group using authentication_page ;

To grant membership to a security group, use the grant access Command.

Syntax:

grant access to security_group ;

To revoke membership to a security group, use the revoke access Command.

Syntax:

revoke access to security_group ;

Note: The semi-colon ; is optional when a Tag contains only a single access Command.

Transactional Locks

The obtain lock Command provides for safe transactional processing. When the named Lock can not be obtained, the request will be queued, and retried for a default of 20 seconds. Lock Directives can set the number of seconds before attempts to obtain a lock timeout, or a landing page to redirect failed requests.

Syntax for Obtain Lock:

obtain lock "lock_name" ;

[ lock_directive; ]…

The release lock Command completes a transaction, allowing the next request waiting on the named Lock to continue.

Syntax for Release Lock:

release lock "lock_name" ;

Cache Templates

Cache Templates allow for the results of complex or costly processes to be saved and reused. Multiple back-ends are supported for storing the results, defaulting to the configured cache Bucket, using reserved keys. Cache Directives can be used to set a different back-end for storing results, or to set an expiration time.

Syntax:

<# start cache for cache_name ;

[ cache_directive; ]…

#>

Template

<# end cache #>

The delete cache Command deletes the stored results for a named Cache Template. The results will be regenerated the next time the Cache Template is processed.

Syntax for Delete Cache Command:

delete cache for cache_name ;

Chance Templates

Chance Templates allow for Random non-deterministic processing of Hashtag Markup.

Syntax:

<# start chance of number % #>

Template

<# end chance #>

Switch Templates

Switch Templates allow for Deterministic processing based on a Variable value.

Syntax:

<# start switch for variable [ as context ]… ; #>

[ <# start case " string " #>

Template

<# end case #> ]…

[ <# start default case #>

Template

<# end case #> ]

<# end switch #>

Call External API

The call Command allows HTTP requests to be sent to external hosts. the default method is POST. The response text is saved to the named to Bucket. If the response is JSON encoded, it will be decoded and stored as individual Variables in the Bucket. The with Bucket values will be sent with the request as JSON.

Syntax:

call "URL" [ using method ] [ with bucket ] to bucket ;



The optional using method can be set to any HTTP request method: GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE.

If the Google API has been configured, any call to the domains spreadsheets.google.com or www.googleapis.com will include a validated OAuth Access Token.

Send Email

The send email Command requires additional Directives. Email Directives set parameters such as to and subject.

Syntax:

send email;

to = "email_address [, email_address]…" ;

[ cc = "email_address [, email_address]…" ; ]

[ bcc = "email_address [, email_address]…" ; ]

[ from_name = "( name | email_address )" ; ]

[ subject = "text" ; ]

[ type = " ( html | text ) " ; ]

[ bodypage = "filepath" ; ]

[ body = """ Template

"""; ]

The Hashtag Framework uses the configured PHP Email settings, however, most email sent directly from web servers will be flagged as spam unless many additional steps are taken. 3rd-party Email delivery providers, such as SendGrid, can be configured to simplify the process. If the Google API has been configured, Email can be delivered through Gmail using the send gmail Command with the same Directives.

Send SMS

The send sms Command is used to send SMS messages. Additional Directives are required to set values for the to phone number, and the message.

Syntax:

send sms;

to = "phone_number" ;

message = "message" ;



SMS messages are delivered through 3rd party providers, such as Twilio or OpenMarket. A from phone number is configured for the Hashtag Framework, along with the SMS provider.

PHP Blocks

The Hashtag Framework supports PHP Blocks in Hashtag Markup. PHP Blocks are evaluated as local functions, so PHP Variables must be declared global to be used in other PHP Blocks. Hashtag Variables are always available in PHP Blocks.

Syntax:

<?php

PHP Template

?>

The Hashtag Framework provides the following PHP Functions while processing PHP Templates:

hashtag_get_value ( $name ) Return the value of a Hashtag Global Variable hashtag_get_local_value ( $name ) Return the value of a Hashtag Local Variable hashtag_set_value ( $name, $value ) Set a Hashtag Global Variable value hashtag_set_local_value ( $name, $value ) Set a Hashtag Local Variable value hashtag_html_dump ( $var, $label=null ) Human readable dump of a PHP Variable in HTML context hashtag_new_uuid ( ) Generate and return a new UUID hashtag_db_query ( $sql ) hashtag_db_exec ( $sql ) hashtag_db_query_params ( $sql, $params=array() ) hashtag_db_fetch ( $result ) hashtag_db_fetch_column ( $result, $column=null ) hashtag_db_fetch_all ( $result ) hashtag_db_get_instance_value ( $instance, $column ) hashtag_db_get_instance ( $instance ) hashtag_db_set_instance_value ( $instance, $column, $value ) hashtag_db_create_instance ( $sql_table_name ) hashtag_db_ensure_table_columns ( $sql_table_name, $columns=array() ) hashtag_db_error ( ) Returns either false, or an array of information about errors from the last query hashtag_insert_row_into_google_sheet_by_id ( $row, $sheet_id, $worksheet=null ) hashtag_insert_row_into_google_sheet_by_name ( $row, $sheet_name, $worksheet=null ) hashtag_process ( $string ) Process a string of text as Hashtag Markup hashtag_empty_bucket ( $bucket ) hashtag_array_to_bucket ( $array, $bucket ) hashtag_bucket_to_array ( $bucket ) hashtag_empty_local_bucket ( $bucket ) hashtag_array_to_local_bucket ( $array, $bucket ) hashtag_local_bucket_to_array ( $bucket ) hashtag_flush_google_sheet_cache ( $namespace=null ) hashtag_ensure_columns_in_google_sheet_by_id ( $columns, $sheet_id, $worksheet=null ) hashtag_ensure_columns_in_google_sheet_by_name ( $columns, $sheet_name, $worksheet=null ) hashtag_ensure_row_id_for_google_sheet_by_id ( $sheet_id, $worksheet=null ) hashtag_ensure_row_id_for_google_sheet_by_name ( $sheet_name, $worksheet=null )

Ready To Go?

Get started developing with Hashtag Markup by using the resources below.

Related Links

Related Projects

These projects use the Hashtag Framework to support Hashtag Markup based platforms.