From: Bill Erickson Date: Thu, 21 Jun 2018 17:06:29 +0000 (-0400) Subject: LP#1775466 more legacy parser; printing; cleanup X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=890a2440a4e9800e033f4e68efda2d96796d3d85;p=working%2FEvergreen.git LP#1775466 more legacy parser; printing; cleanup Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/app.module.ts b/Open-ILS/src/eg2/src/app/app.module.ts index 1f22babfe5..20de8ab017 100644 --- a/Open-ILS/src/eg2/src/app/app.module.ts +++ b/Open-ILS/src/eg2/src/app/app.module.ts @@ -1,7 +1,7 @@ /** * BaseModule is the shared starting point for all apps. It provides - * the root route and core services, and a simple welcome page for users - * that end up here accidentally. + * the root route and a simple welcome page for users that end up here + * accidentally. */ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; @@ -13,7 +13,6 @@ import {BaseComponent} from './app.component'; import {BaseRoutingModule} from './routing.module'; import {WelcomeComponent} from './welcome.component'; -// Import and 'provide' globally required services. @NgModule({ declarations: [ BaseComponent, diff --git a/Open-ILS/src/eg2/src/app/common.module.ts b/Open-ILS/src/eg2/src/app/common.module.ts index dd3c201fff..e04e00309e 100644 --- a/Open-ILS/src/eg2/src/app/common.module.ts +++ b/Open-ILS/src/eg2/src/app/common.module.ts @@ -17,7 +17,6 @@ import {PcrudService} from '@eg/core/pcrud.service'; import {OrgService} from '@eg/core/org.service'; import {AudioService} from '@eg/share/util/audio.service'; import {FormatService} from '@eg/share/util/format.service'; -import {TemplateParserService} from '@eg/share/util/parser.service'; import {PrintService} from '@eg/share/print/print.service'; // Globally available components @@ -73,8 +72,7 @@ export class EgCommonModule { OrgService, PrintService, AudioService, - FormatService, - TemplateParserService + FormatService ] }; } diff --git a/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts b/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts index 7b918ca6b7..e4e64b2d0e 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts @@ -39,7 +39,7 @@ export class CatalogSearchContext { sort: string; fieldClass: string[]; query: string[]; - identQuery: string; + identQuery: string; identQueryType: string; // isbn, issn, etc. joinOp: string[]; matchOp: string[]; diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.ts index 876c2e97eb..3a967a7112 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.ts @@ -80,7 +80,7 @@ export class GridColumnSet { } idlInfoFromDotpath(dotpath: string): any { - if (!dotpath) return null; + if (!dotpath) { return null; } let idlParent; let idlField; @@ -95,7 +95,7 @@ export class GridColumnSet { if (idlField) { if (idlField['class'] && ( - idlField.datatype === 'link' || + idlField.datatype === 'link' || idlField.datatype === 'org_unit')) { idlClass = this.idl.classes[idlField['class']]; } @@ -463,7 +463,7 @@ export class GridContext { for (let i = 0; i < steps.length; i++) { const step = steps[i]; - if (typeof obj != 'object') { + if (typeof obj !== 'object') { // We have run out of data to step through before // reaching the end of the path. Conclude fleshing via // callback if provided then exit. @@ -481,7 +481,7 @@ export class GridContext { obj = this.getObjectFieldValue(obj, step); } - // We found a nested IDL object which may or may not have + // We found a nested IDL object which may or may not have // been configured as a top-level column. Flesh the column // metadata with our newly found IDL info. if (idlField) { diff --git a/Open-ILS/src/eg2/src/app/share/print/print.component.ts b/Open-ILS/src/eg2/src/app/share/print/print.component.ts index ad41e5e950..81b8020d91 100644 --- a/Open-ILS/src/eg2/src/app/share/print/print.component.ts +++ b/Open-ILS/src/eg2/src/app/share/print/print.component.ts @@ -1,6 +1,5 @@ import {Component, OnInit, TemplateRef, ElementRef, Renderer2} from '@angular/core'; import {PrintService, PrintRequest} from './print.service'; -import {TemplateParserService} from '@eg/share/util/parser.service'; @Component({ selector: 'eg-print', @@ -12,21 +11,15 @@ export class PrintComponent implements OnInit { // Template that requires local processing template: TemplateRef; - // TemplateParserService-compatible template string. - templateString: string; - // Context data used for processing the template. context: any; + // Insertion point for externally-compiled templates htmlContainer: Element; - // Final HTML print string - htmlResult: string; - constructor( private renderer: Renderer2, private elm: ElementRef, - private parser: TemplateParserService, private printer: PrintService ) {} @@ -34,54 +27,41 @@ export class PrintComponent implements OnInit { this.printer.onPrintRequest$.subscribe( printReq => this.handlePrintRequest(printReq)); - this.htmlContainer = + this.htmlContainer = this.renderer.selectRootElement('#eg-print-container'); } handlePrintRequest(printReq: PrintRequest) { - this.applyTemplate(printReq).then(ok => { - // Give templates a chance to render before printing - setTimeout(() => this.dispatchPrint(printReq)); - }); - } + this.applyTemplate(printReq); - applyTemplate(printReq: PrintRequest): Promise { - return new Promise((resolve, reject) => { + // Give templates a chance to render before printing + setTimeout(() => this.dispatchPrint(printReq)); + } - if (printReq.template) { - // Inline template. Let Angular do the work. - this.template = printReq.template; - this.context = {$implicit: printReq.contextData}; - resolve(); - } + applyTemplate(printReq: PrintRequest) { - let promise; - if (printReq.templateString) { + if (printReq.template) { + // Inline template. Let Angular do the interpolationwork. + this.template = printReq.template; + this.context = {$implicit: printReq.contextData}; + return; + } - promise = this.parser.apply( - printReq.templateString, printReq.contextData); + if (printReq.text && true /* !this.hatch.isActive */) { + // Insert HTML into the browser DOM for in-browser printing only. - } else if (printReq.htmlString) { - promise = Promise.resolve(printReq.htmlString); + if (printReq.contentType === 'text/plain') { + // Wrap text/plain content in pre's to prevent + // unintended html formatting. + printReq.text = `
${printReq.text}
`; } - promise.then(html => { - this.htmlResult = html; - - if (true /* !this.hatch.isActive */) { - - // Only insert the HTML into the browser DOM when - // printing locally - this.htmlContainer.innerHTML = this.htmlResult; - } - - resolve(); - }); - }); + this.htmlContainer.innerHTML = printReq.text; + } } dispatchPrint(printReq: PrintRequest) { - if (0 /*this.hatch.isActive*/) { + if (0 /* this.hatch.isActive */) { this.printViaHatch(printReq); } else { // Here the needed HTML is already in the page. @@ -91,16 +71,19 @@ export class PrintComponent implements OnInit { printViaHatch(printReq: PrintRequest) { - if (!this.htmlResult) { - // Sometimes the results come from an externally-parsed HTML + if (!printReq.text) { + // Sometimes the results come from an externally-parsed HTML // template, other times they come from an in-page template. - this.htmlResult = this.elm.nativeElement.innerHTML; + printReq.text = this.elm.nativeElement.innerHTML; } + // Send a full HTML document to Hatch + const html = `${printReq.text}`; + /* this.hatch.print({ printContext: printReq.printContext, - content: this.htmlResult + content: html }); */ } diff --git a/Open-ILS/src/eg2/src/app/share/print/print.service.ts b/Open-ILS/src/eg2/src/app/share/print/print.service.ts index 2825af0fb8..e296720048 100644 --- a/Open-ILS/src/eg2/src/app/share/print/print.service.ts +++ b/Open-ILS/src/eg2/src/app/share/print/print.service.ts @@ -2,11 +2,10 @@ import {Injectable, EventEmitter, TemplateRef} from '@angular/core'; export interface PrintRequest { template?: TemplateRef; - templateString?: string; - templateName?: string; // TODO - htmlString?: string, contextData?: any; + text?: string; printContext: string; + contentType?: string; // defaults to text/html } @Injectable() diff --git a/Open-ILS/src/eg2/src/app/share/util/parser.service.ts b/Open-ILS/src/eg2/src/app/share/util/parser.service.ts deleted file mode 100644 index 165866d9cf..0000000000 --- a/Open-ILS/src/eg2/src/app/share/util/parser.service.ts +++ /dev/null @@ -1,291 +0,0 @@ -/** - * AngularJS-style minimal template parser. - * Original use case is supporting AngularJS style print templates. - * Template context data is applied only once at parsing time, there - * is no Angular-style data binding. - * - * Supports the following template constructs: - * {{variables}} - * - * - * - * - */ -import {Injectable, EventEmitter} from '@angular/core'; -import {FormatService} from '@eg/share/util/format.service'; - -// Internal class for modeling a single template parsing instance. -class ParserInstance { - - private context: any; - private format: FormatService; - - // FormatService injected by ParserService - constructor(format: FormatService) { - this.format = format; - } - - // Given an HTML string and an interpolation context/scope, - // process the HTML template declarations, apply variable - // replacements, and return the restulting HTML string. - parse(html: string, context: any): string { - - // Shallow copy the context since we modify the keys internally - this.context = Object.assign({}, context); - - const parser = new DOMParser(); - const doc = parser.parseFromString(html, "text/html"); - - // Parsing as html wraps the content in an wrapper - const domNode = doc.getElementsByTagName('body')[0]; - - this.traverse(domNode); - - return domNode.innerHTML; - } - - // Process each node in the in-progress document. - traverse(node: Node) { - if (!node) return; - - switch (node.nodeType) { - case Node.ELEMENT_NODE: - const complete = this.processElementNode(node as Element); - if (complete) { - return; - } - break; - - case Node.TEXT_NODE: - this.processTextNode(node as Text); - break; - } - - // Array.from() avoids TS compiler warnings - Array.from(node.childNodes).forEach(child => this.traverse(child)); - } - - // Process expressions found on each element. - // Returns true if the node was processed within and needs no - // further processing, false otherwise. - processElementNode(node: Element): boolean { - - const switchExp = node.getAttribute('ng-switch'); - if (switchExp) { - node.removeAttribute('ng-switch'); - if (!this.processSwitchExpression(node, switchExp)) { - // We removed all child nodes in the switch - return true; - } - } - - const ifExp = node.getAttribute('ng-if'); - if (ifExp) { - node.removeAttribute('ng-if'); - if (!this.testIfExpression(ifExp)) { - // A failing IF expression means the node does not render. - node.remove(); - return true; - } - } - - const loopExp = node.getAttribute('ng-repeat'); - if (loopExp) { - node.removeAttribute('ng-repeat'); - this.processLoopExpression(node, loopExp); - return true; - } - - return false; - } - - // Returns true if any of the switch expressions resulted - // in true, thus allowing a child node to remain and require - // future processing. - processSwitchExpression(node: Element, expr: string): boolean { - - // ng-switch only works on string values - const targetVal = this.getContextStringValue(expr); - let matchNode: Node; - - // Find the switch-matching child node - Array.from(node.childNodes).forEach(child => { - if (!matchNode && child.nodeType === Node.ELEMENT_NODE) { - const elm: Element = child as Element; - const val = elm.getAttribute('ng-switch-when'); - if (val === null || val === undefined) { return; } - if (val + '' === targetVal) { - matchNode = child; - } - } - }); - - // Remove all child nodes - while (node.firstChild) { - node.removeChild(node.firstChild); - } - - // Add the matching node back if found - if (matchNode) { - node.appendChild(matchNode); - return true; - } - - return false; // no matching switch values - } - - processLoopExpression(node: Element, expr: string) { - const parts = expr.split('in'); - const listItemKey = parts[0].trim(); - const listPath = parts[1].trim(); - const list = this.getContextValueAt(listPath); - - if (!Array.isArray(list)) { - throw new Error(`Template value ${listPath} is not an Array`); - } - - // Loop over the repeat array and for each iteration, add the - // loop variable as a new value in the (temporary) context so - // it can be referenced in the eval context. - const origContext = this.context; - let prevNode: Element = node; - - list.forEach(listItem => { - const listCtx = {}; - listCtx[listItemKey] = listItem; - this.context = Object.assign(this.context, listCtx); - const newNode = node.cloneNode(true) as Element; - // appendChild used internally when needed. - prevNode.parentNode.insertBefore(newNode, prevNode.nextSibling); - this.traverse(newNode); - prevNode = newNode; - }); - - // recover the original context sans any loop data. - this.context = origContext; - - // get rid of the source/template node - node.remove(); - } - - // To evaluate free-form expressions, create an environment - // where references to elements in the context are available. - // For example: ng-if="foo.bar" -- the 'foo' key from the - // context must be defined in advance for evaluation to succeed. - generateEvalEnv() { - let env = ''; - Object.keys(this.context).forEach(key => { - env += `var ${key} = this.context['${key}'];\n`; - }); - return env; - } - - // Returns true of the IF expression evaluates to true, - // false otherwise. - testIfExpression(expr: string): boolean { - - const env = this.generateEvalEnv(); - const evalStr = `${env}; Boolean(${expr})`; - //console.debug('ng-if eval string: ', evalStr); - - try { - return eval(evalStr); - } catch (err) { - //console.debug('IF expression failed with: ', err); - return false; - } - } - - // Replace variable {{...}} instances in a given Text node - // with the matching data from the context. - processTextNode(node: Text) { - if (!node || !node.data) { return; } - - const matches = node.data.match(/{{.*?}}/g); - if (!matches) { return }; - - matches.forEach(match => { - let dotpath = match.replace(/[{}]/g, ''); - node.replaceData( - node.data.indexOf(match), - match.length, - this.getContextStringValue(dotpath) - ); - }); - } - - // Returns the context item at the given path - getContextValueAt(dotpath: string): any { - let idx; - let obj = this.context; - const parts = dotpath.split('.'); - - for (idx = 0; idx < parts.length; idx++) { - obj = obj[parts[idx]]; - if (obj === null || typeof obj !== 'object') { - break; - } - } - - if (idx < parts.length - 1) { - // Loop exited before inspecting the whole path. - return null; - } - - return obj; - } - - // Find the value within the context at the given path. - getContextStringValue(dotpath: string): string { - - // Variable replacements may contain filters. - const pieces = dotpath.split('|').map(p => p.trim()); - const path = pieces[0] - const filter = pieces[1]; - const data = { - datatype: null, // potentially applied below - value: this.getContextValueAt(path) - }; - - // TODO: teach the format service about processing due dates. - if (filter) { - const fParts = filter.split(':').map(p => p.trim()); - - switch (fParts[0]) { - case 'date': - data.datatype = 'timestamp'; - break; - case 'currency': - data.datatype = 'money'; - break; - case 'limitTo': - const size = fParts[1]; - const offset = fParts[2] || 0; - if (size) { - data.value = - data.value.substring(offset, offset + size); - } - break; - } - } - - return this.format.transform(data); - } -} - - -@Injectable() -export class TemplateParserService { - - constructor(private format: FormatService) {} - - // make async for now, may be needed later - public apply(html: string, context: any): Promise { - const parser = new ParserInstance(this.format); - - return new Promise((resolve, reject) => { - resolve(parser.parse(html, context)); - }); - } -} - diff --git a/Open-ILS/src/eg2/src/app/staff/common.module.ts b/Open-ILS/src/eg2/src/app/staff/common.module.ts index 2dfbb3cd8f..98de443fea 100644 --- a/Open-ILS/src/eg2/src/app/staff/common.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/common.module.ts @@ -12,6 +12,7 @@ import {StringComponent} from '@eg/share/string/string.component'; import {StringService} from '@eg/share/string/string.service'; import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component'; import {DateSelectComponent} from '@eg/share/date-select/date-select.component'; +import {LegacyTemplateService} from '@eg/staff/share/legacy-template.service'; /** * Imports the EG common modules and adds modules common to all staff UI's. @@ -51,6 +52,7 @@ export class StaffCommonModule { return { ngModule: StaffCommonModule, providers: [ // Export staff-wide services + LegacyTemplateService, AccessKeyService, StringService, ToastService diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts index 521db033ca..63bb2fa0de 100644 --- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts @@ -13,7 +13,7 @@ import {OrgService} from '@eg/core/org.service'; import {Pager} from '@eg/share/util/pager'; import {DateSelectComponent} from '@eg/share/date-select/date-select.component'; import {PrintService} from '@eg/share/print/print.service'; -import {TemplateParserService} from '@eg/share/util/parser.service'; +import {LegacyTemplateService} from '@eg/staff/share//legacy-template.service'; @Component({ templateUrl: 'sandbox.component.html' @@ -55,7 +55,7 @@ export class SandboxComponent implements OnInit { private strings: StringService, private toast: ToastService, private printer: PrintService, - private parser: TemplateParserService + private parser: LegacyTemplateService ) {} ngOnInit() { @@ -177,24 +177,16 @@ export class SandboxComponent implements OnInit { cost: 23.3, copies: [ {barcode: 'abc123', title: 'welcome to the jungle', parts: ['a', 'b', 'c']}, - {barcode: 'def456', title: 'hello mudda, hello fadda', parts: ['x','y']} + {barcode: 'def456', title: 'hello mudda, hello fadda', parts: ['x', 'y']} ] - } + }; - /* this.parser.apply(template, context).then(html => { this.printer.print({ - htmlString: html, + text: html, printContext: 'default' }); }); - */ - - this.printer.print({ - templateString: template, - contextData: context, - printContext: 'default' - }); } } diff --git a/Open-ILS/src/eg2/src/app/staff/share/legacy-template.service.ts b/Open-ILS/src/eg2/src/app/staff/share/legacy-template.service.ts new file mode 100644 index 0000000000..fbd6265f5f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/legacy-template.service.ts @@ -0,0 +1,295 @@ +/** + * USING EVAL() OPENS A LARGE SECURITY HOLE. + * DEPRECATE ME. + * AngularJS-style minimal template parser. + * Original use case is supporting AngularJS style print templates. + * Template context data is applied only once at parsing time, there + * is no data binding. + * + * Supports the following template constructs: + * {{variables}} + * + * + * + * + */ +import {Injectable, EventEmitter} from '@angular/core'; +import {FormatService} from '@eg/share/util/format.service'; + +// Internal class for modeling a single template parsing instance. +class ParserInstance { + + private context: any; + private format: FormatService; + + // FormatService injected by ParserService + constructor(format: FormatService) { + this.format = format; + } + + // Given an HTML string and an interpolation context/scope, + // process the HTML template declarations, apply variable + // replacements, and return the restulting HTML string. + parse(html: string, context: any): string { + + // Shallow copy the context since we modify the keys internally + this.context = Object.assign({}, context); + + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + + // Parsing as html wraps the content in an wrapper + const domNode = doc.getElementsByTagName('body')[0]; + + this.traverse(domNode); + + return domNode.innerHTML; + } + + // Process each node in the in-progress document. + traverse(node: Node) { + if (!node) { return; } + + switch (node.nodeType) { + case Node.ELEMENT_NODE: + const complete = this.processElementNode(node as Element); + if (complete) { + return; + } + break; + + case Node.TEXT_NODE: + this.processTextNode(node as Text); + break; + } + + // Array.from() avoids TS compiler warnings + Array.from(node.childNodes).forEach(child => this.traverse(child)); + } + + // Process expressions found on each element. + // Returns true if the node was processed within and needs no + // further processing, false otherwise. + processElementNode(node: Element): boolean { + + const switchExp = node.getAttribute('ng-switch'); + if (switchExp) { + node.removeAttribute('ng-switch'); + if (!this.processSwitchExpression(node, switchExp)) { + // We removed all child nodes in the switch + return true; + } + } + + const ifExp = node.getAttribute('ng-if'); + if (ifExp) { + node.removeAttribute('ng-if'); + if (!this.testIfExpression(ifExp)) { + // A failing IF expression means the node does not render. + node.remove(); + return true; + } + } + + const loopExp = node.getAttribute('ng-repeat'); + if (loopExp) { + node.removeAttribute('ng-repeat'); + this.processLoopExpression(node, loopExp); + return true; + } + + return false; + } + + // Returns true if any of the switch expressions resulted + // in true, thus allowing a child node to remain and require + // future processing. + processSwitchExpression(node: Element, expr: string): boolean { + + // ng-switch only works on string values + const targetVal = this.getContextStringValue(expr); + let matchNode: Node; + + // Find the switch-matching child node + Array.from(node.childNodes).forEach(child => { + if (!matchNode && child.nodeType === Node.ELEMENT_NODE) { + const elm: Element = child as Element; + const val = elm.getAttribute('ng-switch-when'); + if (val === null || val === undefined) { return; } + if (val + '' === targetVal) { + matchNode = child; + } + } + }); + + // Remove all child nodes + while (node.firstChild) { + node.removeChild(node.firstChild); + } + + // Add the matching node back if found + if (matchNode) { + node.appendChild(matchNode); + return true; + } + + return false; // no matching switch values + } + + processLoopExpression(node: Element, expr: string) { + const parts = expr.split('in'); + const listItemKey = parts[0].trim(); + const listPath = parts[1].trim(); + const list = this.getContextValueAt(listPath); + + if (!Array.isArray(list)) { + throw new Error(`Template value ${listPath} is not an Array`); + } + + // Loop over the repeat array and for each iteration, add the + // loop variable as a new value in the (temporary) context so + // it can be referenced in the eval context. + const origContext = this.context; + let prevNode: Element = node; + + list.forEach(listItem => { + const listCtx = {}; + listCtx[listItemKey] = listItem; + this.context = Object.assign(this.context, listCtx); + const newNode = node.cloneNode(true) as Element; + // appendChild used internally when needed. + prevNode.parentNode.insertBefore(newNode, prevNode.nextSibling); + this.traverse(newNode); + prevNode = newNode; + }); + + // recover the original context sans any loop data. + this.context = origContext; + + // get rid of the source/template node + node.remove(); + } + + // To evaluate free-form expressions, create an environment + // where references to elements in the context are available. + // For example: ng-if="foo.bar" -- the 'foo' key from the + // context must be defined in advance for evaluation to succeed. + generateEvalEnv() { + let env = ''; + Object.keys(this.context).forEach(key => { + env += `var ${key} = this.context['${key}'];\n`; + }); + return env; + } + + // Returns true of the IF expression evaluates to true, + // false otherwise. + testIfExpression(expr: string): boolean { + + const env = this.generateEvalEnv(); + const evalStr = `${env}; Boolean(${expr})`; + // console.debug('ng-if eval string: ', evalStr); + + try { + return eval(evalStr); + } catch (err) { + // console.debug('IF expression failed with: ', err); + return false; + } + } + + // Replace variable {{...}} instances in a given Text node + // with the matching data from the context. + processTextNode(node: Text) { + if (!node || !node.data) { return; } + + const matches = node.data.match(/{{.*?}}/g); + if (!matches) { return; } + + matches.forEach(match => { + const dotpath = match.replace(/[{}]/g, ''); + node.replaceData( + node.data.indexOf(match), + match.length, + this.getContextStringValue(dotpath) + ); + }); + } + + // Returns the context item at the given path + getContextValueAt(dotpath: string): any { + let idx; + let obj = this.context; + const parts = dotpath.split('.'); + + for (idx = 0; idx < parts.length; idx++) { + obj = obj[parts[idx]]; + if (obj === null || typeof obj !== 'object') { + break; + } + } + + if (idx < parts.length - 1) { + // Loop exited before inspecting the whole path. + return null; + } + + return obj; + } + + // Find the value within the context at the given path. + getContextStringValue(dotpath: string): string { + + // Variable replacements may contain filters. + const pieces = dotpath.split('|').map(p => p.trim()); + const path = pieces[0]; + const filter = pieces[1]; + const data = { + datatype: null, // potentially applied below + value: this.getContextValueAt(path) + }; + + // TODO: teach the format service about processing due dates. + if (filter) { + const fParts = filter.split(':').map(p => p.trim()); + const fName = fParts[0]; + const fArgs = fParts.slice(1); + + switch (fName) { + case 'date': + data.datatype = 'timestamp'; + break; + case 'currency': + data.datatype = 'money'; + break; + case 'limitTo': + const size = fArgs[0]; + if (size) { + const offset = fArgs[1] || 0; + data.value = + data.value.substring(offset, offset + size); + } + break; + } + } + + return this.format.transform(data); + } +} + + +@Injectable() +export class LegacyTemplateService { + + constructor(private format: FormatService) {} + + // make async for now, may be needed later + public apply(html: string, context: any): Promise { + const parser = new ParserInstance(this.format); + + return new Promise((resolve, reject) => { + resolve(parser.parse(html, context)); + }); + } +} +