import * as Handlebars from 'handlebars' ; import he from 'he' ; import Remarkable from 'remarkable' ; import { MAX_PASSES } from '../../config' ; import { Abi , ContractMetadata , RicardianClause , RicardianContract , RicardianContractConfig , RicardianContractProcessor , Transaction , TransactionAction , } from '../../interfaces' ; import { RicardianContractRenderError } from '../../RicardianContractRenderError' ; import { extractSymbolCode , hasVariable , indexTransaction , sanitizeHtml , tagMetadataVariables , tagTemplateVariables , } from './helpers' ; import { RicardianContext } from './RicardianContext' ; import { validateIcon , validateSummary , validateTitle } from './validators' ; import * as utils from '../../utils/contractUtils' ; import { getContractSpecVersion } from '../../utils/contractUtils' ; const implVersion = { major : 0 , minor : 0 , } ; /**
 * Processes a Ricardian contract, interpolating transaction variables and clauses, and
 * extracting metadata and html.
 */ export class RicardianContractProcessorImpl implements RicardianContractProcessor { private allowUnusedVariables = false ; private disableMetadataValidation = false ; private wrappedHelpers : string [ ] = [ 'lookup' ] ; /**
     * Constructs the RicardianContractProcessorImpl.
     */ constructor ( ) { Handlebars . registerHelper ( 'wrap' , function ( options ) : string { return `<div class="variable ${ options . hash . class }">` + options . fn ( this ) + '</div>' ; } ) ; this . registerWrappedHelper ( 'symbol_to_symbol_code' , ( symbol ) => { return new Handlebars . SafeString ( extractSymbolCode ( symbol ) ) ; } ) ; this . registerWrappedHelper ( 'asset_to_symbol_code' , ( asset ) => { return new Handlebars . SafeString ( extractSymbolCode ( asset ) ) ; } ) ; Handlebars . registerHelper ( 'nowrap' , ( text ) => { return new Handlebars . SafeString ( text ) ; } ) ; } protected registerWrappedHelper ( name , fn ) { Handlebars . registerHelper ( name , fn ) ; this . wrappedHelpers . push ( name ) ; } protected registerHelper ( name , fn ) { Handlebars . registerHelper ( name , fn ) ; } public getSpecVersion ( ) { return implVersion ; } param config public process ( config ) { const version = getContractSpecVersion ( config ) ; const specVersion = this . getSpecVersion ( ) ; if ( version . major === specVersion . major && version . minor <= specVersion . minor ) { return this . processContract ( config ) ; } else { throw new RicardianContractRenderError ( `Unexpected version encountered. ` + `Found ${ version }` ) ; } } protected processContract ( config ) { this . allowUnusedVariables = ! ! config . allowUnusedVariables ; this . disableMetadataValidation = ! ! config . allowUnusedVariables ; const maxPasses = config . maxPasses || MAX_PASSES ; const action = utils . getTransactionAction ( config . transaction , config . actionIndex ) ; const rawTemplate = utils . getContractTextFromAbi ( config . abi , action ) ; const context = this . createContext ( config . abi , config . transaction , action ) ; const { metadata , interpolatedRicardian } = this . interpolateContract ( rawTemplate , context , maxPasses ) ; if ( ! metadata . spec_version ) { metadata . spec_version = '0.0.0' ; } const html = this . convertToHtml ( interpolatedRicardian ) ; return { getMetadata : ( ) => metadata , getHtml : ( ) => html } ; } return private createContext ( abi , transaction , action ) { const indexedTransaction = indexTransaction ( transaction ) ; const ricardianClauses = abi . ricardian_clauses ; return { ... action . data , $transaction : indexedTransaction , $action : action , $clauses : ricardianClauses . reduce ( ( clauses , current ) : any => ( { ... clauses , [ current . id ] : current . body , } ) , { } ) , } as RicardianContext ; } param context param maxPasses return private interpolateContract ( rawTemplate , context , maxPasses ) { const { metadata : rawMetadata , content : rawContent } = utils . getMetadataAndContent ( rawTemplate ) ; return { metadata : this . interpolateMetadata ( rawMetadata , context , maxPasses ) , interpolatedRicardian : this . interpolateRicardian ( rawContent , context , maxPasses ) , } ; } param contractContent param context param maxPasses return private interpolateRicardian ( contractContent , context , maxPasses ) { let interpolatedRicardian = contractContent ; let passes = 0 ; const allowUnusedVariables = this . allowUnusedVariables ; do { try { interpolatedRicardian = tagTemplateVariables ( this . wrappedHelpers , interpolatedRicardian ) ; const handlebarsTemplate = Handlebars . compile ( interpolatedRicardian , { strict : ! allowUnusedVariables } ) ; interpolatedRicardian = handlebarsTemplate ( context ) ; } catch ( e ) { throw new RicardianContractRenderError ( `Handlebars error: ${ e . message }` ) ; } passes ++ ; } while ( passes < maxPasses && hasVariable ( interpolatedRicardian ) ) ; if ( ! this . allowUnusedVariables && passes === maxPasses && hasVariable ( interpolatedRicardian ) ) { throw new RicardianContractRenderError ( 'Contract has uninterpolated variables after maximum number of variable interpolation passes.' ) ; } return interpolatedRicardian ; } param metadata param context param maxPasses return private interpolateMetadata ( metadata , context , maxPasses ) { const { title , icon , ... metadataTemplate } = metadata ; let interpolatedMetadata = metadataTemplate ; let passes = 0 ; const allowUnusedVariables = this . allowUnusedVariables ; let hasUninterpolatedVars = false ; if ( ! this . disableMetadataValidation ) { validateTitle ( title ) ; validateIcon ( icon ) ; } do { try { hasUninterpolatedVars = false ; interpolatedMetadata = tagMetadataVariables ( this . wrappedHelpers , interpolatedMetadata ) ; Object . keys ( interpolatedMetadata ) . forEach ( ( key ) => { const value = interpolatedMetadata [ key ] ; if ( ! ! value ) { const hbMetaTplt = Handlebars . compile ( value , { strict : ! allowUnusedVariables } ) ; interpolatedMetadata [ key ] = hbMetaTplt ( context ) ; hasUninterpolatedVars = hasUninterpolatedVars || hasVariable ( interpolatedMetadata [ key ] ) ; } } ) ; } catch ( e ) { throw new RicardianContractRenderError ( `Handlebars error: ${ e . message }` ) ; } passes ++ ; } while ( passes < maxPasses && hasUninterpolatedVars ) ; if ( ! this . allowUnusedVariables && passes === maxPasses && hasUninterpolatedVars ) { throw new RicardianContractRenderError ( 'Contract has uninterpolated metadata variables after maximum number of variable interpolation passes.' ) ; } if ( ! this . disableMetadataValidation ) { validateSummary ( interpolatedMetadata . summary ) ; } return { title , icon , ... interpolatedMetadata } as ContractMetadata ; } param content return private convertToHtml ( content ) { const md = new Remarkable ( 'commonmark' , { breaks : true , html : false , } ) ; let html = md . render ( content ) ; html = html . replace ( /<p>/g , '' ) ; html = html . replace ( /<\/p>/g , '<br />' ) ; html = he . decode ( html ) ; html = sanitizeHtml ( html ) ; return html ; } } export default RicardianContract ;