if (fcf.isServer()){
  var libFS = require('fs');
  var libUtil = require('util');
}

fcf.module({
  name: "fcf:NRender/NDetails/Loader.js",
  dependencies: ["fcf:NRender/NDetails/Helper.js"],
  module: function(helper) {
    var NDetails = fcf.prepareObject(fcf, "NRender.NDetails");
    fcf.addException("ERROR_RENDER_SERVER_ONLY_TEMPLATE",   "The '${{template}}$' template can only be rendered on the server");
    fcf.addException("ERROR_RENDER_TEMPLATE_NOT_FOUND",     "Element '${{template}}$' template not found");

    var innerTemplateStorage = {};
    var innerDependencies = {};


    NDetails.Loader = class Loader {

      constructor(a_options) {
        this._options = fcf.append({}, a_options);
      }

      setSettings(a_options) {
        fcf.append(this._options, a_options);
      }

      load(a_path, a_state, a_id, a_cb) {
        let currentTemplateInfo = fcf.getContext().currentTemplate;
        let date = new Date();
        if (typeof a_state === "function" || arguments.length == 1){
          a_cb = a_state;
          a_state = {
            theme: fcf.application.getTheme(),
            include: {},
          }
        }
        let themeName = a_state && a_state.theme ? a_state.theme : undefined;
        a_path = helper.normalizeTemplatePath(a_path, themeName).split("+")[0];

        return fcf.actions()
        .then(async ()=>{
          let template = await this._loadTemplateData(a_path, a_state, a_id, a_path);

          fcf.getContext().currentTemplate = currentTemplateInfo;
          let lsttmpl = fcf.NDetails.currentTemplate;
          fcf.NDetails.currentTemplate = a_path;

          template = this._resolveTemplate(template, a_path);

          fcf.NDetails.currentTemplate = lsttmpl;
          fcf.getContext().currentTemplate = currentTemplateInfo;

          if (!fcf.empty(template.options.access) && !fcf.access(template.options.access))
            throw new fcf.Exception("ERROR_ACCESS", {resource: a_path, responseCode: 403});

          if (a_cb)
            a_cb(undefined, template);
          return template;
        })
        .catch((a_error)=>{
          if (a_cb)
            a_cb(a_error);
        });
      }

      loadInfo(a_path, a_state, a_id, a_cb) {
        let currentTemplateInfo = fcf.getContext().currentTemplate;
        let date = new Date();
        if (typeof a_state === "function" || arguments.length == 1){
          a_cb = a_state;
          a_state = {
            theme: fcf.application.getTheme(),
            include: {},
          }
        }
        let themeName = a_state && a_state.theme ? a_state.theme : undefined;
        a_path = helper.normalizeTemplatePath(a_path, themeName).split("+")[0];
        return fcf.actions()
        .then(async ()=>{
          let template = await this._loadTemplateData(a_path, a_state, a_id, a_path);

          fcf.getContext().currentTemplate = currentTemplateInfo;

          if (!fcf.empty(template.options.access) && !fcf.access(template.options.access)){
            throw new fcf.Exception("ERROR_ACCESS", {resource: a_path, responseCode: 403});
          }

          if (a_cb)
            a_cb(undefined, template);
          return template;
        })
        .catch((a_error)=>{
          if (a_cb)
            a_cb(a_error);
        });
      }


      _resolveTemplate(a_template, a_path){
        let result = fcf.append({}, a_template);
        result.templates = fcf.append({}, result.templates);
        for(let block in result.templates) {
          result.templates[block] = fcf.append({}, result.templates[block]);

          let args = {};
          for(let i = 0; i < result.templates[block].rawArguments.length; ++i){
            let currentArgs = fcf.scriptExecutor.parse(result.templates[block].rawArguments[i].data, {}, result.templates[block].rawArguments[i].path, result.templates[block].rawArguments[i].startLine);
            fcf.append(args, currentArgs);
          }

          result.templates[block].arguments = args;
          result.templates[block].hardDependencies = {};
          for(let key in result.templates[block].arguments){
            if (!fcf.isArg(result.templates[block].arguments[key]))
              continue;
            if (!result.templates[block].arguments[key].hardDependencies)
              continue;
            if (!(key in result.templates[block].hardDependencies))
              result.templates[block].hardDependencies[key] = [];
            fcf.append(result.templates[block].hardDependencies[key], result.templates[block].arguments[key].hardDependencies);
          }

          if ("wrapper" in result.templates[block].options && !result.templates[block].options.wrapper)
            result.templates[block].arguments.fcfWrapper = false;
        }

        return result;
      }

      async _loadTemplateData(a_path, a_state, a_id, a_mainTemplate){
        let self = this;
        let originPath = a_path;
        a_path = fcf.getPath(a_path);

        if (innerTemplateStorage[a_path])
          return innerTemplateStorage[a_path];

        let data = await fcf.load({path: a_path, aliases: a_state.theme.getAliases()})
        let stat = await libUtil.promisify(libFS.stat)(a_path);
        let lastModifyTime = stat.mtime;

        //Loading template data
        let lstct = fcf.NDetails.currentTemplate;
        fcf.NDetails.currentTemplate = a_mainTemplate;
        let template = this._parseTemplate(data, a_path, originPath, a_state, a_id, fcf.getContext());
        template.lastModifyTime = lastModifyTime;
        fcf.NDetails.currentTemplate = lstct;

        //Filling in the need for a wrapper file
        await fcf.each(template.templates, async (a_key, a_subTemplate)=>{
          let dotPos = a_path.lastIndexOf(".");
          if (dotPos == -1)
            return;
          let path = a_path.substring(0, dotPos);
          if (a_key)
            path += "+" + a_key;
          path += ".wrapper.js";
          if (a_subTemplate.options && a_subTemplate.options.clientRendering)
            a_subTemplate.wrapperExists = true;
          else
            a_subTemplate.wrapperExists = await libUtil.promisify(libFS.exists)(fcf.getPath(path));
        });


        //Loading template hooks
        let error = undefined;
        fcf.each(template.templates, (a_key, a_subTemplate)=>{
          let hookFileArr = a_path.split(".");
          hookFileArr.pop();
          if (a_key != "")
            hookFileArr[hookFileArr.length-1] += "+" + a_key;
          hookFileArr.push("hooks");
          hookFileArr.push("js");
          let hookFilePath = hookFileArr.join(".");
          fcf.requireEx([hookFilePath], {showError: false}, (error, hooks)=>{
            if (error && error.toString().indexOf("SyntaxError") !== -1){
              error = error;
              return false;
            }
            try {
              let stat = libFS.statSync(fcf.getPath(hookFilePath));
              template.lastModifyTime = fcf.max(template.lastModifyTime, stat.mtime);
            } catch(e){
            }

            if (hooks){
              fcf.append(a_subTemplate.hooksFiles, [hookFilePath]);
              fcf.append(a_subTemplate.hooksDependenciesArgs, fcf.NDetails.modules[hookFilePath].dependenciesArgs)
              fcf.append(a_subTemplate.hooksDependencies,     fcf.NDetails.modules[hookFilePath].dependencies)
              if (typeof hooks.hooksProgrammableArgument !== "object")
                hooks.hooksProgrammableArgument = {};
              if (typeof hooks.hooksBeforeArgument !== "object")
                hooks.hooksBeforeArgument = {};
              if (typeof hooks.hooksAfterArgument !== "object")
                hooks.hooksAfterArgument = {};
              fcf.append(a_subTemplate.hooks, hooks);
            }


          })
        });

        if (error)
          throw error;

        //Apply the inherited functional
        if (template.options.extends){
          let extendsPath = fcf.getPath(template.options.extends);
          if (!innerDependencies[extendsPath])
            innerDependencies[extendsPath] = {};
          innerDependencies[extendsPath][a_path] = a_path;

          let etemplate = await this._loadTemplateData(template.options.extends, a_state, a_id, a_mainTemplate);

          template = this._inheritance(etemplate, template.options.extends, template);
        }

        template = fcf.clone(template);
        fcf.each(template.templates, (a_name, a_templateInfo)=>{
          a_templateInfo.template.template = self._templateItemsToJS(a_templateInfo.template.templateItems, a_templateInfo.options, a_templateInfo);
        });

        //Save into local buffer
        let initWatcher = !innerTemplateStorage[a_path];
        innerTemplateStorage[a_path] = template;

        //Set watch file
        if (initWatcher) {
          function clClearStorage(a_path){
            let obj = innerDependencies[a_path];
            delete innerDependencies[a_path];
            fcf.scriptExecutor.clear(a_path);
            delete innerTemplateStorage[a_path];
            fcf.each(obj, (a_depPath) => {
              clClearStorage(a_depPath);
            })
          }

          libFS.watchFile(a_path, {interval: 1000}, (a_eventType, a_filename)=>{
            clClearStorage(a_path);
          });
        }


        return template;
      }

      _inheritance(a_baseTemplate, a_baseTemplatePath, a_template){
        let self = this;
        let result = {
          options: {},
          templates: {},
          inheritance: [],
        };
        fcf.append(true, result.options, a_baseTemplate.options);
        fcf.append(true, result.options, a_template.options);
        fcf.append(result.inheritance, a_baseTemplate.inheritance, a_template.inheritance);
        result.inheritance.push(a_baseTemplatePath);

        let names = {};
        fcf.each(a_template.templates, function(k){names[k] = true;});
        fcf.each(a_baseTemplate.templates, function(k){names[k] = true;});
        for(let tname in names){
          if (!result.templates[tname])
            result.templates[tname] = {
              arguments:            {},
              options:              {},
              rawArguments:         [],
              template:             { items: [], template: "", templateItems: [] },
              hooks:                {},
              hooksFiles:           [],
              hooksDependencies:    [],
              hooksDependenciesArgs:[],
              hardDependencies:     {},
              wrapperExists:        false,
              runtimeArgs:          {},
              runtime:              {},
            };

          let rawArgs;

          //build hooksFiles
          rawArgs = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].hooksFiles ? a_baseTemplate.templates[tname].hooksFiles : [];
          fcf.append(result.templates[tname].hooksFiles, rawArgs);
          rawArgs = a_template.templates[tname] && a_template.templates[tname].hooksFiles ? a_template.templates[tname].hooksFiles : [];
          fcf.append(result.templates[tname].hooksFiles, rawArgs);


          //build hooksDependencies
          rawArgs = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].hooksDependencies ? a_baseTemplate.templates[tname].hooksDependencies : [];
          fcf.append(result.templates[tname].hooksDependencies, rawArgs);
          rawArgs = a_template.templates[tname] && a_template.templates[tname].hooksDependencies ? a_template.templates[tname].hooksDependencies : [];
          fcf.append(result.templates[tname].hooksDependencies, rawArgs);

          //build hooksDependenciesArgs
          rawArgs = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].hooksDependenciesArgs ? a_baseTemplate.templates[tname].hooksDependenciesArgs : [];
          fcf.append(result.templates[tname].hooksDependenciesArgs, rawArgs);
          rawArgs = a_template.templates[tname] && a_template.templates[tname].hooksDependenciesArgs ? a_template.templates[tname].hooksDependenciesArgs : [];
          fcf.append(result.templates[tname].hooksDependenciesArgs, rawArgs);

          // build raw arguments
          rawArgs = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].rawArguments ? a_baseTemplate.templates[tname].rawArguments : [];
          fcf.append(result.templates[tname].rawArguments, rawArgs);
          rawArgs = a_template.templates[tname] && a_template.templates[tname].rawArguments ? a_template.templates[tname].rawArguments : [];
          fcf.append(result.templates[tname].rawArguments, rawArgs);

          //build options
          if (a_baseTemplate.templates[tname] && !fcf.empty(a_baseTemplate.templates[tname].options)){
            if (typeof a_baseTemplate.templates[tname].options == "object") {
              for(let property in a_baseTemplate.templates[tname].options){
                if (property == "include"){
                  if (!result.templates[tname].options.include)
                    result.templates[tname].options.include = [];
                  fcf.append(result.templates[tname].options.include, a_baseTemplate.templates[tname].options.include);
                }
                else if (property == "clientInclude"){
                  if (!result.templates[tname].options.clientInclude)
                    result.templates[tname].options.clientInclude = [];
                  fcf.append(result.templates[tname].options.clientInclude, a_baseTemplate.templates[tname].options.clientInclude);
                } else {
                  result.templates[tname].options[property] = a_baseTemplate.templates[tname].options[property];
                }
              }
            }
          }
          if (a_template.templates[tname] && !fcf.empty(a_template.templates[tname].options)){
            if (typeof a_template.templates[tname].options == "object") {
              for(let property in a_template.templates[tname].options){
                if (property == "include"){
                  if (!result.templates[tname].options.include)
                    result.templates[tname].options.include = [];
                  fcf.append(result.templates[tname].options.include, a_template.templates[tname].options.include);
                }
                else if (property == "clientInclude"){
                  if (!result.templates[tname].options.clientInclude)
                    result.templates[tname].options.clientInclude = [];
                  fcf.append(result.templates[tname].options.clientInclude, a_template.templates[tname].options.clientInclude);
                } else {
                  result.templates[tname].options[property] = a_template.templates[tname].options[property];
                }
              }
            }
          }

          //build wrapperExists
          if ((a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].wrapperExists) ||
              (a_template.templates[tname] && a_template.templates[tname].wrapperExists) ||
              (result.templates[tname].options.clientRendering))
            result.templates[tname].wrapperExists = true;

          // build hardDependencies
          let baseHardDependencies = a_baseTemplate.templates[tname] && a_baseTemplate.templates[tname].hardDependencies ? a_baseTemplate.templates[tname].hardDependencies : undefined;
          let hardDependencies     = a_template.templates[tname] && a_template.templates[tname].hardDependencies ? a_template.templates[tname].hardDependencies : undefined;
          fcf.append(result.templates[tname].hardDependencies, baseHardDependencies, hardDependencies);


          // build templates
          if (a_baseTemplate.templates[tname] && !fcf.empty(a_baseTemplate.templates[tname].template))
            result.templates[tname].template = a_baseTemplate.templates[tname].template;
          if (a_template.templates[tname] && !fcf.empty(a_template.templates[tname].template))
            result.templates[tname].template = a_template.templates[tname].template;

            // build runtime
            result.templates[tname].runtime = {};
            if (a_baseTemplate.templates[tname] && !fcf.empty(a_baseTemplate.templates[tname].runtime))
              result.templates[tname].runtime = a_baseTemplate.templates[tname].runtime;
            if (a_template.templates[tname] && !fcf.empty(a_template.templates[tname].runtime))
              result.templates[tname].runtime = a_template.templates[tname].runtime;

            // build runtimeArgs
            result.templates[tname].runtimeArgs = {};
            if (a_baseTemplate.templates[tname] && !fcf.empty(a_baseTemplate.templates[tname].runtimeArgs))
              result.templates[tname].runtimeArgs = a_baseTemplate.templates[tname].runtimeArgs;
            if (a_template.templates[tname] && !fcf.empty(a_template.templates[tname].runtimeArgs))
              result.templates[tname].runtimeArgs = a_template.templates[tname].runtimeArgs;

          // build hooks
          if (a_baseTemplate.templates[tname] && !fcf.empty(a_baseTemplate.templates[tname].hooks)){
            fcf.append(result.templates[tname].hooks, a_baseTemplate.templates[tname].hooks);
            if (!result.templates[tname].hooks.prototype)
              result.templates[tname].hooks.prototype = {};
            result.templates[tname].hooks.prototype = a_baseTemplate.templates[tname].hooks;
          }
          if (a_template.templates[tname] && !fcf.empty(a_template.templates[tname].hooks)){
            fcf.append(result.templates[tname].hooks, a_template.templates[tname].hooks);
            let dstHooks = result.templates[tname].hooks;
            let srcHooks = dstHooks.prototype;
            if (srcHooks) {
              if (!fcf.empty(srcHooks.hooksProgrammableArgument)){
                for(let key in srcHooks.hooksProgrammableArgument){
                  if (!(key in dstHooks.hooksProgrammableArgument)){
                    dstHooks.hooksProgrammableArgument[key] = srcHooks.hooksProgrammableArgument[key];
                  }
                }
              }
              if (!fcf.empty(srcHooks.hooksBeforeArgument)){
                for(let key in srcHooks.hooksBeforeArgument)
                  if (!(key in dstHooks.hooksBeforeArgument))
                    dstHooks.hooksBeforeArgument[key] = srcHooks.hooksBeforeArgument[key];
              }
              if (!fcf.empty(srcHooks.hooksAfterArgument)){
                for(let key in srcHooks.hooksAfterArgument)
                  if (!(key in dstHooks.hooksAfterArgument))
                    dstHooks.hooksAfterArgument[key] = srcHooks.hooksAfterArgument[key];
              }
            }
          }
        }
        result.lastModifyTime = fcf.max(a_baseTemplate.lastModifyTime, a_template.lastModifyTime);

        return result;
      }

      _parseTemplate(a_ctxt, a_path, a_originPath, a_state, a_id, a_context) {
        a_ctxt = this._uncomment(a_ctxt);
        let currentTemplate = fcf.NDetails.currentTemplate;
        var result = {
          options:   {},
          templates: {},
        };

        var blocksPositions = [];
        var lstpos = 0;
        do {
          var blockPositions = this._getBlockPositions(a_ctxt, lstpos);
          lstpos = blockPositions.dataEnd;
          if (lstpos) {
            blocksPositions.push(blockPositions);
          }
        } while (lstpos);

        for (var i = 0; i < blocksPositions.length; ++i) {
          this._parseBlockMainOptions(result, a_ctxt, blocksPositions[i], a_path);
        }

        fcf.NDetails.currentTemplate = currentTemplate;
        for (var i = 0; i < blocksPositions.length; ++i) {
          this._parseBlock(result, a_ctxt, blocksPositions[i], a_path, a_originPath);
        }

        fcf.each(result.templates, (a_key, a_subtemplateInfo)=>{
          if (!("clientRendering" in a_subtemplateInfo.options) && "clientRendering" in result.options)
            a_subtemplateInfo.options.clientRendering = result.options.clientRendering;
          if (!("wrapper" in a_subtemplateInfo.options) && "wrapper" in result.options)
            a_subtemplateInfo.options.wrapper = result.options.wrapper;
          if (!("merge" in a_subtemplateInfo.options) && "merge" in result.options)
            a_subtemplateInfo.options.merge = result.options.merge;

          let include = Array.isArray(a_subtemplateInfo.options.include) ? a_subtemplateInfo.options.include   :
                        !fcf.empty(a_subtemplateInfo.options.include)    ? [a_subtemplateInfo.options.include] :
                                                                           [];
          let jsInclude = [];
          for(let i = 0; i < include.length; ++i) {
            let ext = fcf.getExtension(include[i]).toLowerCase();
            let filePath = include[i];
            if (filePath[0] != "/" && filePath.indexOf(":") == -1){
              filePath = fcf.getDirectory(a_originPath) + "/" + filePath;
              if (filePath[0] != "/" && filePath.indexOf(":") == -1)
                filePath = ":" + filePath;
            }
            include[i] = filePath;
            if (ext == "js"){
              jsInclude.push(include[i]);
            }
          }

          a_subtemplateInfo.options.include = include;

          if (!fcf.empty(jsInclude)){
            fcf.require(jsInclude);
          }

          include = Array.isArray(a_subtemplateInfo.options.clientInclude) ? a_subtemplateInfo.options.clientInclude :
                    !fcf.empty(a_subtemplateInfo.options.clientInclude)    ? [a_subtemplateInfo.options.clientInclude] :
                                                                          [];
          for(let i = 0; i < include.length; ++i) {
            let ext = fcf.getExtension(include[i]).toLowerCase();
            let filePath = include[i];

            if (filePath[0] != "/" && filePath.indexOf(":") == -1){
              filePath = fcf.getDirectory(a_originPath) + "/" + filePath;
              if (filePath[0] != "/" && filePath.indexOf(":") == -1)
                filePath = ":" + filePath;
            }
            include[i] = filePath;
          }
          a_subtemplateInfo.options.clientInclude = include;
        });

        return result;
      }

      _uncomment(a_text){
        let result     = "";
        let lstpos     = 0;
        let lstposfind = 0;
        while(true) {
          let nextpos = -1;
          let pos = a_text.indexOf("//~", lstposfind);
          if (pos != -1){
            let posspace = this._searchSpace(a_text, pos+3);
            if (posspace != -1){
              let cmd = a_text.substring(pos+3, posspace);
              if (cmd == "TEMPLATE" || cmd == "OPTIONS" || cmd == "ARGUMENTS"){
                lstposfind = posspace;
                continue;
              }
            }
            nextpos = a_text.indexOf("\n", pos);
          }
          result += a_text.substring(lstpos, pos != -1 ? pos : a_text.length);
          if (nextpos != -1) {
            lstpos = nextpos;
            lstposfind = nextpos;
          } else {
            break;
          }
        };
        return result;
      }

      _searchSpace(a_text, a_startPos){
        if (!a_startPos)
          a_startPos = 0;
        for(let i = a_startPos; i < a_text.length; ++i) {
          if (a_text.charCodeAt(i) <= 32) {
            return i;
          }
        }
        return -1;
      }
      _parseBlockMainOptions(a_dst, a_ctxt, a_blockPositions, a_path) {
        let headerLine = a_ctxt.substr(a_blockPositions.headerBeg+3, a_blockPositions.headerEnd - a_blockPositions.headerBeg-3);
        let arrHeaderLine = fcf.splitSpace(headerLine);
        let type = arrHeaderLine[0] ? arrHeaderLine[0].toUpperCase() : "";
        if (type == "OPTIONS" && arrHeaderLine.length == 1) {
          let data = a_ctxt.substr(a_blockPositions.dataBeg, a_blockPositions.dataEnd - a_blockPositions.dataBeg);
          let stringNumber = this._getStringNumber(a_ctxt, a_blockPositions.dataBeg);
          let options = fcf.scriptExecutor.parse(data, {}, a_path, stringNumber);
          fcf.append(a_dst.options, options);
        }
      }

      _parseBlock(a_dst, a_ctxt, a_blockPositions, a_path, a_originPath) {
        let headerLine = a_ctxt.substr(a_blockPositions.headerBeg+3, a_blockPositions.headerEnd - a_blockPositions.headerBeg-3);
        let arrHeaderLine = fcf.splitSpace(headerLine);
        let type = arrHeaderLine[0] ? arrHeaderLine[0].toUpperCase() : "";
        let name = arrHeaderLine[1] ? arrHeaderLine[1] : "";
        if (!a_dst.templates[name]){
          a_dst.templates[name] = {
            options:              {},
            arguments:            {},
            rawArguments:         [],
            template:             {},
            hooks:                {},
            hooksFiles:           [],
            hooksDependencies:    [],
            hooksDependenciesArgs:[],
            hardDependencies:     {},
            runtimeArgs:          {},
            runtime:              {},
          };
        }
        a_originPath = a_originPath.split("+")[0];
        if (type == "OPTIONS") {
          let data = a_ctxt.substr(a_blockPositions.dataBeg, a_blockPositions.dataEnd - a_blockPositions.dataBeg);
          let stringNumber = this._getStringNumber(a_ctxt, a_blockPositions.dataBeg);
          fcf.append(a_dst.templates[name].options, fcf.scriptExecutor.parse(data, {}, a_path, stringNumber));
        } else if (type == "ARGUMENTS") {
          let data = a_ctxt.substr(a_blockPositions.dataBeg, a_blockPositions.dataEnd - a_blockPositions.dataBeg);
          let stringNumber = this._getStringNumber(a_ctxt, a_blockPositions.dataBeg);
          a_dst.templates[name].rawArguments.push({data: data, path: a_path, startLine: stringNumber});
        } else if (type == "TEMPLATE") {
          let content = a_ctxt.substr(a_blockPositions.dataBeg, a_blockPositions.dataEnd - a_blockPositions.dataBeg);
          a_dst.templates[name].template.originPath     = !name ? a_originPath : a_originPath + "+" + name;
          a_dst.templates[name].template.templateItems  = this._contentToItems(this._removeLastLF(content), a_dst.templates[name].template.originPath);
          a_dst.templates[name].template.path           = a_path;
          a_dst.templates[name].template.stringNumber   = this._getStringNumber(a_ctxt, a_blockPositions.dataBeg);
        }
      }

      _getStringNumber(a_txt, a_position){
        if (a_position === undefined)
          a_position = a_txt.length;
        let pos = undefined;
        let lastpos = 0;
        let n   = 0;
        while(true) {
          pos = a_txt.indexOf("\n", pos !== undefined ? pos+1 : 0);
          if (pos == -1 || pos > a_position)
            return n;
          ++n;
          lastpos = pos;
        }
      }

      _getBlockPositions(a_ctxt, a_start) {
        var result = {
          headerBeg: undefined,
          headerEnd: undefined,
          dataBeg: undefined,
          dataEnd: undefined,
        };
        var pos;
        while(true) {
          pos = a_ctxt.indexOf("//~", a_start);
          if (pos != -1 && pos != 0 && a_ctxt[pos-1] != "\n") {
            a_start = pos;
            continue;
          }
          if (pos == -1)
            return result;
          var dpos = pos+3;
          if (dpos >= a_ctxt.length)
            return result;
          var epos = dpos;
          while (a_ctxt.charCodeAt(epos) > 32 && epos < a_ctxt.length)
            ++epos;
          if (epos >= a_ctxt.length)
            return result;
          var type = a_ctxt.substring(dpos, epos).toLowerCase();
          var validBlocks = {"template":true, "options":true, "arguments":true};
          if (!(type in validBlocks)){
            a_start = epos;
            continue;
          }
          break;
        }

        result.headerBeg = pos;
        if (result.headerBeg == -1){
          result.headerBeg = undefined;
          return result;
        }

        result.headerEnd = a_ctxt.indexOf("\n", pos);
        if (result.headerEnd == -1){
          result.headerEnd = a_ctxt.length;
        }

        result.dataBeg = (result.headerEnd + 1) > a_ctxt.length ? result.headerEnd
                                                                : result.headerEnd + 1;
        var lstpos = result.dataBeg;
        while (true) {
          pos = a_ctxt.indexOf("//~", lstpos);
          if (pos != -1 && a_ctxt[pos-1] != "\n") {
            lstpos = pos+1;
            continue;
          }
          break;
        }
        result.dataEnd = pos != -1 ? pos : a_ctxt.length;

        return result;
      }

      _removeLastLF(a_content){
        if (a_content.length >= 2 && a_content.charAt(a_content.length-2) == "\r" && a_content.charAt(a_content.length-1) == "\n")
          return a_content.substr(0, a_content.length-2);
        if (a_content.length >= 1 && a_content.charAt(a_content.length-1) == "\n")
          return a_content.substr(0, a_content.length-1);
        return a_content;
      }

      _contentToItems(a_content, a_templatePath){
        let items = [];
        let pos = 0;
        let startPos = 0;
        let rridPos = -1;
        let firtsrid = false;
        while(pos != -1) {
          while(pos != -1){
            pos = a_content.indexOf("{{", pos);
            if (a_content[pos-1] == "$" ||  a_content[pos-1] == "#" || a_content[pos-1] == "%" || a_content[pos-1] == "@" || a_content[pos-1] == "!"){
              break;
            } else if (pos != -1){
              pos += 2;
            }
          }
          if (rridPos != -1 && (pos == -1 || pos > rridPos)) {
            if (rridPos - startPos){
              items.push({type: "content", data: a_content.substr(startPos, rridPos - startPos), start: startPos});
            }
            items.push({type: "rrid", rid: firtsrid});
            firtsrid = false;
            startPos = rridPos;
            pos = rridPos;
            rridPos = -1;
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "$") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1), start: startPos});
            let endPos = this._getCommandEnd(a_content, pos-1);
            if (endPos != -1)
              endPos -= 3;
            if (endPos != -1) {
              items.push({type: "variable", data: fcf.trim(a_content.substr(pos+2, endPos - pos - 2))});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "#") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1), start: startPos});
            let endPos = this._getCommandEnd(a_content, pos-1);
            if (endPos != -1)
              endPos -= 3;
            if (endPos != -1) {
              let info = this._getTemplatePositionInfo(a_content, 0, pos-1, a_templatePath)
              let rid;
              let rtype;
              if (info.state == 1) {
                if (!info.inscribed) {
                  rid = `${a_templatePath}:${pos}`;
                  rtype = 1;
                } else {
                  rid = `${a_templatePath}:${info.tagEnd}`;
                  if (!items[items.length-2] || items[items.length-2].rid != rid){
                    items[items.length-1].data = items[items.length-1].data.substr(0, items[items.length-1].data.length-1);
                    items.push({type: "rrid", rid: rid});
                    items.push({type: "content", data: ">", start: info.tagEnd});
                  }
                  rtype = 3;
                }
              } else if (!firtsrid) {
                firtsrid = `${a_templatePath}:${info.tagEnd}`;
                rid = firtsrid;
                rtype = 2;
                rridPos = info.tagEnd;
              } else {
                rid = firtsrid;
                rtype = 2;
                rridPos = info.tagEnd;
              }
              let options = {};
              if (info.state != 1) {
                options.index       = info.currentControlBlock;
                options.mainIndex   = info.mainControlBlock;
                if (info.currentControlBlock == info.mainControlBlock){
                  options.attribute   = info.attribute;
                }
              } else {
                options.index       = 0;
                options.mainIndex   = 0
              }
              items.push({type: "runtime", rtype: rtype, options: options, rid: rid, data: fcf.trim(a_content.substr(pos+2, endPos - pos - 2))});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "@") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1), start: startPos});
            let endPos = this._getCommandEnd(a_content, pos-1);
            if (endPos != -1)
              endPos -= 3;
            if (endPos != -1) {
              let code         = helper.insertCallPositions(a_content, "template", "", pos+2, endPos, true);
              items.push({type: "calculation", data: fcf.trim(fcf.trim(code), [";"])});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "!") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1), start: startPos});
            let endPos = this._getCommandEnd(a_content, pos-1);
            if (endPos != -1)
              endPos -= 3;
            if (endPos != -1) {
              items.push({type: "translate", data: fcf.trim(a_content.substr(pos+2, endPos - pos - 2))});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos !== -1 && pos !== 0 && a_content[pos-1] == "%") {
            if (pos - startPos - 1)
              items.push({type: "content", data: a_content.substr(startPos, pos - startPos - 1), start: startPos});
            let endPos = this._getCommandEnd(a_content, pos-1);
            if (endPos != -1)
              endPos -= 3;
            if (endPos != -1) {
              let code         = helper.insertCallPositions(a_content, "template", "", pos+2, endPos);
              items.push({type: "code", data: code});
              pos = endPos+3;
              startPos = pos;
            } else {
              pos = -1;
            }
          } else if (pos == -1) {
            if (a_content.length - startPos)
              items.push({type: "content", data: a_content.substr(startPos, a_content.length - startPos), start: startPos});
            pos = -1;
          } else {
            pos = pos+2;
          }
        }

        return items;
      }

      _getTemplatePositionInfo(a_content, a_start, a_end, a_templatePath){
        let i = a_start;
        let STATE_CONTENT       = 1
        let STATE_BLOCK_SPACE   = 2;
        let STATE_BLOCK_TAG     = 3;
        let STATE_ATTRIBUTE     = 4;
        let STATE_WAIT_VALUE    = 5;
        let STATE_WAIT_VALUE_EQ = 6;
        let STATE_VALUE         = 7;
        let state = {
          state:                STATE_CONTENT,
          inscribed:            false,
          tagEnd:               -1,
          tag:                  undefined,
          currentControlBlock:  0,
          mainControlBlock:     -1,
          attribute:       [],
          q:                    false,
          dq:                   false,
          cc:                   0,
          cm:                   {},
          stop:                 false,
        };
        while(i < a_content.length) {
          let c = a_content[i];
          if (  state.state == STATE_BLOCK_TAG     ||
                state.state == STATE_BLOCK_SPACE   ||
                state.state == STATE_ATTRIBUTE     ||
                state.state == STATE_WAIT_VALUE_EQ ||
                state.state == STATE_WAIT_VALUE    ||
                (state.state == STATE_VALUE        && !state.dq && !state.q)
              ) {
            if (c == ">") {
              state.tagEnd = i;
              if (i >= a_end) {
                break;
              }
              state.state = STATE_CONTENT;
              state.tag = undefined;
              state.attribute = [];
              state.q = false;
              state.dq = false;
              state.dq = false;
            }
          }

          if (state.state == STATE_CONTENT) {
            let lte = -1;
            let t3 = false;
            state.tagEnd = -1;
            state.lastTagEnd = state.tagEnd;
            if (c == "<") {
              state.state = STATE_BLOCK_TAG;
              state.cm = {};
              state.cc = 0;
              state.currentControlBlock = 0;
              state.mainControlBlock = -1;
            } else if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
              state.cc = 0;
              state.currentControlBlock = state.cc;
              if (state.mainControlBlock == -1)
                state.mainControlBlock = state.currentControlBlock;
              let ep = this._getCommandEnd(a_content, i);
              if (a_content[i-2] != "/>" && a_content[i-1] == ">" && a_content[ep] == "<"){
                lte = i-1;
              }
              i = ep - 1;
            }
            if (i >= a_end) {
              if (lte != -1){
                state.inscribed = true;
                state.tagEnd = lte;
              }
              break;
            }
          } else if (state.state == STATE_BLOCK_TAG) {
            if (state.tag === undefined)
              state.tag = "";
            if (c.charCodeAt(0) <= 32){
              state.state = STATE_BLOCK_SPACE;
            } else if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
              state.cm[c] = true;
              if (fcf.count(state.cm) > 1)
                throw new fcf.Exception("ERROR_TEMPLATE_MULTIPLE_CONTROL_IN_ATTR", {template: a_templatePath});
              if (i <= a_end){
                state.currentControlBlock = state.cc;
                if (state.mainControlBlock == -1)
                  state.mainControlBlock = state.currentControlBlock;
              }
              ++state.cc;
              let ep = this._getCommandEnd(a_content, i);
              state.tag += a_content.substring(i, ep);
              i = ep - 1;
            } else {
              state.tag += a_content[i];
            }
          } else if (state.state == STATE_BLOCK_SPACE) {
            if (i <= a_end){
              state.mainControlBlock = -1;
            }
            if (c == "/"){
              if ( i>=a_end ) {
                state.stop = true;
              }
            } else if (c.charCodeAt(0) <= 32){
              if ( i>=a_end ) {
                state.stop = true;
              }
            } else {
              if (c == "\""){
                state.dq = true;
                if (!state.stop){
                  state.attribute = [];
                }
              } else if (c == "'"){
                state.q = true;
                if (!state.stop){
                  state.attribute = [];
                }
              } else {
                state.cm = {};
                if (!state.stop){
                  if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
                    state.cm[c] = true;
                    if (fcf.count(state.cm) > 1)
                      throw new fcf.Exception("ERROR_TEMPLATE_MULTIPLE_CONTROL_IN_ATTR", {template: a_templatePath});
                    state.attribute = [state.cc];
                    if (i <= a_end){
                      state.currentControlBlock = state.cc;
                      if (state.mainControlBlock == -1)
                        state.mainControlBlock = state.currentControlBlock;
                    }
                    ++state.cc;
                    let ep = this._getCommandEnd(a_content, i);
                    i = ep - 1;
                  } else {
                    state.attribute = [c];
                  }
                } else {
                  if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
                    let ep = this._getCommandEnd(a_content, i);
                    i = ep - 1;
                  }
                }
              }
              state.state = STATE_ATTRIBUTE;
            }
          } else if (state.state == STATE_ATTRIBUTE) {
            if (c.charCodeAt(0) <= 32 && !state.dq && !state.q){
              state.state = STATE_WAIT_VALUE_EQ;
            } else if (c == "=" && !state.dq && !state.q){
              if (typeof state.attribute[state.attribute.length-1] !== "string")
                state.attribute.push("");
              state.attribute[state.attribute.length-1] += "=";
              state.state = STATE_WAIT_VALUE;
            } else if (c == "'" && state.q){
              state.q = false;
              state.state = STATE_WAIT_VALUE_EQ;
            } else if (c == "\"" && state.dq){
              state.dq = false;
              state.state = STATE_WAIT_VALUE_EQ;
            } else if (!state.stop){
              if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{") {
                state.cm[c] = true;
                if (fcf.count(state.cm) > 1)
                  throw new fcf.Exception("ERROR_TEMPLATE_MULTIPLE_CONTROL_IN_ATTR", {template: a_templatePath});
                state.attribute.push(state.cc);
                if (i <= a_end){
                  state.currentControlBlock = state.cc;
                  if (state.mainControlBlock == -1)
                    state.mainControlBlock = state.currentControlBlock;
                }
                ++state.cc;
                let ep = this._getCommandEnd(a_content, i);
                i = ep-1;
              } else {
                if (typeof state.attribute[state.attribute.length-1] !== "string")
                  state.attribute.push("");
                state.attribute[state.attribute.length-1] += c;
              }
            } else {
              if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
                let ep = this._getCommandEnd(a_content, i);
                i = ep - 1;
              }
            }
          } else if (state.state == STATE_WAIT_VALUE_EQ){
            if (c.charCodeAt(0) > 32){
              if (c == "="){
                state.state = STATE_WAIT_VALUE
              } else if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{") {
                state.cm[c] = true;
                if (fcf.count(state.cm) > 1)
                  throw new fcf.Exception("ERROR_TEMPLATE_MULTIPLE_CONTROL_IN_ATTR", {template: a_templatePath});
                if (i <= a_end){
                  state.currentControlBlock = state.cc;
                  if (state.mainControlBlock == -1)
                    state.mainControlBlock = state.currentControlBlock;
                }
                ++state.cc;
                let ep = this._getCommandEnd(a_content, i);
                i = ep-1;
              } else {
                state.state = STATE_BLOCK_SPACE;
                continue;
              }
            }
          } else if (state.state == STATE_WAIT_VALUE){
            if (c == "\""){
              state.dq = true;
              state.state = STATE_VALUE;
              if (typeof state.attribute[state.attribute.length-1] !== "string")
                state.attribute.push("");
              state.attribute[state.attribute.length-1] += "\"";
            } else  if (c == "'"){
              state.q = true;
              state.state = STATE_VALUE;
              if (typeof state.attribute[state.attribute.length-1] !== "string")
                state.attribute.push("");
              state.attribute[state.attribute.length-1] += "\"";
            } else if (c.charCodeAt(0) <= 32){
              state.state = STATE_WAIT_VALUE;
            } else {
              state.state = STATE_VALUE;
              if (typeof state.attribute[state.attribute.length-1] !== "string")
                state.attribute.push("");
              state.attribute[state.attribute.length-1] += "\"";
              if (!state.stop){
                if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
                  state.cm[c] = true;
                  if (fcf.count(state.cm) > 1)
                    throw new fcf.Exception("ERROR_TEMPLATE_MULTIPLE_CONTROL_IN_ATTR", {template: a_templatePath});
                  state.attribute.push(state.cc);
                  if (i <= a_end) {
                    state.currentControlBlock = state.cc;
                    if (state.mainControlBlock == -1)
                      state.mainControlBlock = state.currentControlBlock;
                  }
                  ++state.cc;
                  let ep = this._getCommandEnd(a_content, i);
                  i = ep - 1;
                } else {
                  if (typeof state.attribute[state.attribute.length-1] !== "string")
                    state.attribute.push("");
                  state.attribute[state.attribute.length-1] += c;
                }
              } else {
                if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
                  let ep = this._getCommandEnd(a_content, i);
                  i = ep - 1;
                }
              }
            }
          } else if (state.state == STATE_VALUE){
            if (state.dq && c == "\""){
              state.dq = false;
              state.state = STATE_BLOCK_SPACE;
              if (typeof state.attribute[state.attribute.length-1] !== "string")
                state.attribute.push("");
              state.attribute[state.attribute.length-1] += "\"";
            } else if (state.q && c == "'"){
              state.q = false;
              state.state = STATE_BLOCK_SPACE;
              if (typeof state.attribute[state.attribute.length-1] !== "string")
                state.attribute.push("");
              state.attribute[state.attribute.length-1] += "\"";
            } else if (!state.dq && !state.q && c.charCodeAt(0) <= 32){
              state.state = STATE_BLOCK_SPACE;
              if (typeof state.attribute[state.attribute.length-1] !== "string")
                state.attribute.push("");
              state.attribute[state.attribute.length-1] += "\"";
            } else {
              if (!state.stop){
                if ((c == "!" || c == "@" || c == "#" || c == "$"  || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
                  state.cm[c] = true;
                  if (fcf.count(state.cm) > 1)
                    throw new fcf.Exception("ERROR_TEMPLATE_MULTIPLE_CONTROL_IN_ATTR", {template: a_templatePath});
                  state.attribute.push(state.cc);
                  if (i <= a_end) {
                    state.currentControlBlock = state.cc;
                    if (state.mainControlBlock == -1)
                      state.mainControlBlock = state.currentControlBlock;
                  }
                  ++state.cc;
                  let ep = this._getCommandEnd(a_content, i);
                  i = ep - 1;
                } else {
                  if (typeof state.attribute[state.attribute.length-1] !== "string")
                    state.attribute.push("");
                  state.attribute[state.attribute.length-1] += c;
                }
              } else {
                if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{"){
                  let ep = this._getCommandEnd(a_content, i);
                  i = ep - 1;
                }
              }
            }
          }
          ++i;
        }
        return state;
      }

      _getCommandEnd(a_content, a_start){
        let slashCounter = 0;
        let stack = [];
        let state = "";
        let i = a_start;
        while(i < a_content.length){
          if (a_content[i-1] == "\\"){
            ++slashCounter;
          } else {
            slashCounter = 0;
          }
          let c = a_content[i]
          if (state == "!" || state == "@" || state == "#" || state == "$" || state == "%") {
            if (c == "}" && a_content[i+1] == "}" && state == a_content[i+2]) {
              i += 3;
              stack.pop();
              if (stack.length == 0){
                return i;
              }
              state = stack[stack.length-1];
              continue;
            } else if ((c == "!" || c == "@" || c == "#" || c == "$" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{") {
              state = c;
              stack.push(c);
              i += 3;
              continue;
            } else if ((c == "\"" || c == "'" || c== "`") && (slashCounter % 2 == 0) && (state == "@" || state == "#" || state == "$" || state == "%") ){
              state = c;
            }
          } else if ((state == "\"" || state == "'" || state== "`") && (slashCounter % 2 == 0)) {
            if (c == state)
              state = stack[stack.length-1];
          } else {
            if ((c == "!" ||c == "@" || c == "#" || c == "$" || c == "%" || c == "%") && a_content[i+1] == "{" && a_content[i+2] == "{") {
              state = c;
              stack.push(c);
              i += 3;
              continue;
            }
          }
          ++i;
        }
        return -1;
      }

      _templateItemsToJS(a_items, a_options, a_subTemplate){
        a_items = a_items ? a_items : [];
        let result = "var _2318115_block_env={args: args, route: route, decor: decor};";
        for (var i = 0; i < a_items.length; ++i) {
          if (a_items[i].type == "code"){
            result += a_items[i].data;
            result += ";";
          } else if (a_items[i].type == "variable") {
            result += "render.write(fcf.resolve(_2318115_block_env, " + JSON.stringify(a_items[i].data) + ") );";
          } else if (a_items[i].type == "calculation") {
            result += "render.write(" + a_items[i].data + ");";
          } else if (a_items[i].type == "rrid") {
            result += "render.writeRuntime('', \"" + a_items[i].rid + "\", {}, 0);";
          } else if (a_items[i].type == "runtime") {
            let variables = fcf.getVariablesFromCode(a_items[i].data);
            let inlineVariables = [];
            for(let j = 0; j < variables.length; ++j){
              let varArr = variables[j].split(".");
              if (varArr[0] != "args" || !varArr[1]){
                inlineVariables.push(varArr[0]);
                continue;
              }
              if (!(varArr[1] in a_subTemplate.runtimeArgs)) {
                a_subTemplate.runtimeArgs[varArr[1]] = [];
              }
              a_subTemplate.runtimeArgs[varArr[1]].push(a_items[i].rid);
            }
            if (!a_subTemplate.runtime[a_items[i].rid])
              a_subTemplate.runtime[a_items[i].rid] = [];
            let runtime = {};
            runtime.code      = a_items[i].data;
            runtime.variables = variables;
            runtime.type      = a_items[i].rtype;
            if (a_items[i].rtype == 2) {
              if (a_items[i].options.attribute) {
                runtime.attribute = a_items[i].options.attribute;
                runtime.value     = a_items[i].options.value;
              }
              runtime.index     = a_items[i].options.index;
              runtime.mainIndex = a_items[i].options.mainIndex;
            }
            a_subTemplate.runtime[a_items[i].rid].push(runtime);
            result += "render.writeRuntime(" + a_items[i].data + ", \"" + a_items[i].rid + "\", "
            result += "{";
            for(let i = 0; i < inlineVariables.length; ++i)
              result += JSON.stringify(inlineVariables[i]) + ":" + inlineVariables[i] + ", ";
            result += "}, ";
            result += a_items[i].rtype;
            result += ");";

            a_items[i].rid
          } else if (a_items[i].type == "translate") {
            result +="(()=>{";
            result +="  let content947339 = fcf.t(" + JSON.stringify(a_items[i].data) + ");";
            result +="  let contentIndex947339 = 0;";
            result +="  while(contentIndex947339 < content947339.length && contentIndex947339 != -1) {";
            result +="    let posStart947339 = content947339.indexOf('@{{', contentIndex947339);";
            result +="    render.write(";
            result +="      content947339.substr(contentIndex947339, posStart947339 != -1 ? (posStart947339 - contentIndex947339) : (content947339.length - contentIndex947339))";
            result +="    );";
            result +="    let posEnd947339 = content947339.indexOf('}}@', posStart947339);";
            result +="    if (posStart947339 == -1 || posEnd947339 == -1)";
            result +="      break;";
            result +="    render.write(eval(fcf.trim(content947339.substr(posStart947339 + 3, (posEnd947339 - posStart947339) - 3), ';')));";
            result +="    contentIndex947339 = posEnd947339 + 3;";
            result +="  }";
            result +="})();";
          } else if (a_items[i].type == "content") {
            let content = a_items[i].data;
            if (a_options.merge)
              content = this._insertMergeIndexs(content, a_items[i].start);
            result += "render.write(" + JSON.stringify(content) + ");";
            let lines  = this._getStringNumber(content);
            for(let i = 0; i < lines; ++i)
              result += "\n";
          }
        }
        return result;
      }

      _insertMergeIndexs(a_content, a_startPos){
        let result = "";
        let pos = 0;
        let newpos = 0;
        while(true){
          newpos = a_content.indexOf("<", pos);
          if (newpos == -1){
            result += a_content.substring(pos);
            break;
          }
          result += a_content.substring(pos, newpos);
          while(newpos < a_content.length && a_content.charCodeAt(newpos) > 32 && a_content[newpos] != ">"){
            result += a_content[newpos];
            ++newpos;
          }
          --newpos;
          let attribute = ` fcfmrj="${newpos + a_startPos}"`;
          result += attribute;
          pos = newpos+1;
        }
        return result;
      }

    }

    return NDetails.Loader;
  }
});
