--- /dev/null
+import {Injectable, EventEmitter} from '@angular/core';
+import {FormatService} from '@eg/share/util/format.service';
+
+class ParserInstance {
+
+ private context: any;
+ private domNode: HTMLElement;
+ private format: FormatService;
+ private evalEnv: string;
+
+ // 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 {
+ this.context = context;
+ const parser = new DOMParser();
+
+ const doc = parser.parseFromString(html, "text/html");
+
+ // Parsing as html wraps the content in an <html><body> wrapper
+ this.domNode = doc.getElementsByTagName('body')[0];
+
+ this.traverse(this.domNode);
+
+ return this.domNode.innerHTML;
+ }
+
+ // Process each node in the source document.
+ traverse(node: Node) {
+ if (!node) return;
+
+ switch (node.nodeType) {
+ case Node.ELEMENT_NODE:
+ this.processElement(node as Element);
+ break;
+
+ case Node.TEXT_NODE:
+ this.replaceText(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.
+ processElement(node: Element) {
+
+ const ifExp = node.getAttribute('ng-if');
+ if (ifExp && !this.testIfExpression(ifExp)) {
+ // A failing IF expression means the node does not render.
+ node.remove();
+ }
+
+ const loopExp = node.getAttribute('ng-repeat');
+ if (loopExp) {
+ this.processLoopExpression(node, loopExp);
+ }
+ }
+
+ processLoopExpression(node: Element, expr: string) {
+ const parts = expr.split('in');
+ const key = parts[0].trim();
+ const listPath = parts[1].trim();
+
+ this.generateEvalEnv();
+ const list = this.getContextValueAt(listPath);
+
+ if (!Array.isArray(list)) {
+ throw new Error(`Template value ${listPath} is not an Array`);
+ }
+ }
+
+ // 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() {
+ if (this.evalEnv) return;
+ this.evalEnv = '';
+ Object.keys(this.context).forEach(key => {
+ this.evalEnv += `var ${key} = this.context['${key}'];\n`;
+ });
+ }
+
+ // Returns true of the IF expression evaluates to true,
+ // false otherwise.
+ testIfExpression(expr: string): boolean {
+
+ this.generateEvalEnv();
+ const evalStr = `${this.evalEnv}; 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.
+ replaceText(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 {
+ const value = this.getContextValueAt(dotpath);
+ // TODO IDL stuff when possible for formatting (e.g. dates)
+ return this.format.transform({value: value});
+ }
+}
+
+
+@Injectable()
+export class TemplateParserService {
+
+ constructor(private format: FormatService) {}
+
+ // make async for now, may be needed later
+ public apply(html: string, context: any): Promise<string> {
+ const parser = new ParserInstance(this.format);
+
+ return new Promise((resolve, reject) => {
+ resolve(parser.parse(html, context));
+ });
+ }
+}
+
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';
@Component({
templateUrl: 'sandbox.component.html'
private pcrud: PcrudService,
private strings: StringService,
private toast: ToastService,
- private printer: PrintService
+ private printer: PrintService,
+ private parser: TemplateParserService
) {}
ngOnInit() {
if (sort.length) {
orderBy.cbt = sort[0].name + ' ' + sort[0].dir;
}
-
+
return this.pcrud.retrieveAll('cbt', {
offset: pager.offset,
limit: pager.limit,
.then(txt => this.toast.success(txt));
}, 4000);
}
+
+
+
+ testParser() {
+
+ const template = `
+ <div>
+ <div>This item needs to be routed to
+ <b>{{dest_location.shortname}} AKA {{dest_location.name}}</b>
+ </div>
+ <div ng-if="foo">i should disappear</div>
+ <div ng-if="boolFalse">i should also disappear</div>
+ <div ng-if="boolTrue">i should stick around</div>
+ <div ng-if="dest_address">
+ <div>{{dest_address.street1}}</div>
+ <div>{{dest_address.street2}}</div>
+ </div>
+ <div>Slip Date: {{today}}</div>
+ <ol>
+ <li ng-repeat="copy in copies">
+ <div>Barcode: {{copy.barcode}}</div>
+ <div>Title: {{copy.title}}</div>
+ </li>
+ </ol>
+ `;
+
+ const context = {
+ dest_location: {
+ shortname: 'BR1',
+ name: 'Branch Uno',
+ },
+ dest_address: {
+ street1: '123 Pineapple Rd',
+ street2: 'APT #3',
+ },
+ boolTrue: true,
+ boolFalse: false,
+ today: new Date(),
+ copies: [
+ {barcode: 'abc123', title: 'welcome to the jungle'},
+ {barcode: 'def456', title: 'hello mudda, hello fadda'}
+ ]
+ }
+
+ this.parser.apply(template, context).then(html => {
+ console.log('parsed: ', html);
+ });
+ }
}