text: /*!
  GPII Settings Handler Utilities
  
  Copyright 2013 OCAD University
  
  Licensed under the New BSD license. You may not use this file except in
  compliance with this License.
  
  You may obtain a copy of the License at
  https://github.com/gpii/universal/LICENSE.txt
  */
  
  var fluid = fluid || require("infusion"),
      xm = require("xml-mapping"),
      $ = fluid.registerNamespace("jQuery");
  
  var XMLHandler = fluid.registerNamespace("gpii.settingsHandlers.XMLHandler");
  
  XMLHandler.applyPaths = function (operation, pathOp, paths) {
      for (var i = 0; i < paths.length; ++ i) {
          operation === "push" ? pathOp.push(paths[i]) : pathOp.pop();
      }
  };
  
  XMLHandler.expandInnerValues = function (inputPath, outputPath, expander, innerValues) {
      var inputPrefixOp = expander.inputPrefixOp;
      var outputPrefixOp = expander.outputPrefixOp;
      var apply = XMLHandler.applyPaths;
      
      apply("push", inputPrefixOp, inputPath);
      apply("push", outputPrefixOp, outputPath);
      var expanded = {};
      fluid.each(innerValues, function (innerValue) {
          var expandedInner = expander.expand(innerValue);
          $.extend(true, expanded, expandedInner);
      });
      apply("pop", outputPrefixOp, outputPath);
      apply("pop", inputPrefixOp, inputPath);
      
      return expanded;
  };
  
  /* -------- Transformation functions -------------------- */
      
  /**
   * Transforms the given array to an object.
   * Uses the expandSpec.options.key values from each object within the array as new keys.
   *
   * For example, with expandSpec.options.key = "name" and an input object like this:
   *
   * {
   *   b: [
   *     { name: b1, v: v1 },
   *     { name: b2, v: v2 }
   *   ]
   * }
   *
   * The output will be:
   * {
   *   b: {
   *     b1: {
   *       v: v1
   *     }
   *   },
   *   {
   *     b2: {
   *       v: v2
   *     }
   *   }
   * }
   */
  
  fluid.defaults("gpii.settingsHandlers.XMLHandler.arrayToObject", {
      gradeNames: ["fluid.standardTransformFunction", "fluid.lens" ],
      invertConfiguration: "gpii.settingsHandlers.XMLHandler.arrayToObject.invertRules"
  });
  
  XMLHandler.arrayToObject = function (arr, expander, expandSpec) {
      if (fluid.get(expandSpec, "options.key") === undefined) {
          fluid.fail("arrayToObject requires a 'key' option.", expandSpec);
      }
      if (!fluid.isArrayable(arr)) {
          fluid.fail("arrayToObject didn't find array at inputPath.", expandSpec);
      }
      var newHash = {};
      var pivot = expandSpec.options.key;
  
      fluid.each(arr, function (v, k) {
          //check that we have a pivot entry in the object and it's a valid type:            
          var newKey = v[pivot];
          var keyType = typeof(newKey);
          if (keyType !== "string" && keyType !== "boolean" && keyType !== "number") {
              fluid.fail("arrayToObject encountered untransformable array due to missing or invalid key", v);
          }
          //use the value of the key element as key and use the remaining content as value
          var content = fluid.copy(v);
          delete content[pivot];
          //fix sub Arrays if needed:
          if (expandSpec.options.innerValue) {
              content = XMLHandler.expandInnerValues([expander.outputPrefix, k.toString()], 
                  [newKey], expander, expandSpec.options.innerValue);
          }
          newHash[newKey] = content;
      });
      return newHash;
  };
  
  XMLHandler.arrayToObject.invertRules = function (expandSpec, expander) {
      var togo = fluid.copy(expandSpec);
      togo.type = "gpii.settingsHandlers.XMLHandler.objectToArray";
      togo.inputPath = fluid.model.composePaths(expander.outputPrefix, expandSpec.outputPath);
      togo.outputPath = fluid.model.composePaths(expander.inputPrefix, expandSpec.inputPath);
      //invert expanders from innerValue as well:
      // TODO: The Model Transformations framework should be capable of this, but right now the
      // issue is that we use a "private contract" to operate the "innerValue" slot. We need to
      // spend time thinking of how this should be formalised
      if (togo.options && togo.options.innerValue) {
          var innerValue = togo.options.innerValue;
          for (var i = 0; i < innerValue.length; ++i) {
              innerValue[i] = fluid.model.transform.invertConfiguration(innerValue[i]);
          }            
      }
      return togo;
  };
  
  fluid.defaults("gpii.settingsHandlers.XMLHandler.objectToArray", {
      gradeNames: "fluid.standardTransformFunction"
  });
  
  /**
   * Transforms an object into array of objects.
   * This performs the inverse transform of gpii.settingsHandlers.XMLHandler.arrayToObject.
   */
  XMLHandler.objectToArray = function (hash, expander, expandSpec) {
      if (fluid.get(expandSpec, "options.key") === undefined) {
          fluid.fail("objectToArray requires a 'key' option.", expandSpec);
      }
      
      var newArray = [];
      var pivot = expandSpec.options.key;
  
      fluid.each(hash, function (v, k) {
          var content = {};
          content[pivot] = k;
          if (expandSpec.options.innerValue) {
              v = XMLHandler.expandInnerValues([expandSpec.inputPath, k], [expandSpec.inputPath, newArray.length.toString()], 
                  expander, expandSpec.options.innerValue);
          }
          // TODO: remove this use of fluid.merge which will not be valid in 1.5 Infusion framework
          content = fluid.merge("replace", content, v);
          newArray.push(content);
      });
      return newArray;
  };
  
  /* ------------------ XML handler specific ---------------------- */
  //Used when fluid.get'ing and set'ing, to allow escaped strings
  // TODO: When FLUID-4852 is fixed, remove these definitions
  var getParser = {
      parser: {
          parse: fluid.pathUtil.parseEL,
          compose: fluid.pathUtil.composePath
      },
      strategies: [fluid.model.defaultFetchStrategy]
  };
  
  var setParser = {
      parser: {
          parse: fluid.pathUtil.parseEL,
          compose: fluid.pathUtil.composePath
      },
      strategies: [fluid.model.defaultFetchStrategy, fluid.model.defaultCreatorStrategy]
  };
  
  /**
   * "Pretty-prints" an XML string.
   *
   * This function is derived from sample code posted by Dan Brooks on Stack Overflow:
   * http://stackoverflow.com/questions/376373/pretty-printing-xml-with-javascript
   *
   * @param {String} xml the XML string to reformat
   * @return {String} the reformatted XML document
   */
  XMLHandler.formatXml = function (xml) {
      var formatted = "";
      var reg = /(>)(<)(\/*)/g;
      xml = xml.replace(reg, "$1\r\n$2$3");
      var pad = 0;
      fluid.each(xml.split("\r\n"), function(node) {
          var indent = 0;
          if (node.match( /.+<\/\w[^>]*>$/ )) {
              indent = 0;
          } else if (node.match( /^<\/\w/ )) {
              if (pad !== 0) {
                  pad -= 1;
              }
          } else if (node.match( /^<\w[^>]*[^\/]>.*$/ )) {
              indent = 1;
          } else {
              indent = 0;
          }
  
          var padding = "";
          for (var i = 0; i < pad; i++) {
              padding += "  ";
          }
  
          formatted += padding + node + "\r\n";
          pad += indent;
      });
      return formatted;
  };
  
  /**
   * Gets a settings array and a json-ified version of the XML,
   * sets each the relevant settings and returns the settings object with
   * {oldValue: XXXX, newValue: YYYY} as values
   * WARNING: Destructively modifies obj
   */
  XMLHandler.applySettings = function (settings, obj) {
      var returnPayload = {};
      //edit each of the settings
      fluid.each(settings, function(value, el) {
          //store the previous value
          var prev = fluid.get(obj, el, getParser);            
          fluid.set(obj, el, value, setParser);
          
          //create entry in the return payload
          returnPayload[el] = { oldValue: prev, newValue: value };
      });
      return returnPayload;                
  };
  
  /**
   * Convert XML into JSON.
   * In case options.rules is present, trasform with those rules.
   * NOTE: array transformed to objects if given in knownArrays or detected
   * as using 'name' as pivot element.
   */
  XMLHandler.XMLtoJSON = function (xml, options) {
      // Parse XML to JSON.
      var json = xm.tojson(xml);
      //  Apply the settings
      return (options && options.rules) ? fluid.model.transformWithRules(json, options.rules) : json;
  };
  
  /**
   * Convert JSON to XML.
   * In case options.rules is present, first tranform JSON with invert rules.
   */
  XMLHandler.JSONtoXML = function (json, options) {
      options = options || {};
      // Create XML - first line is the xml-tag from options if set.
      var xml = options["xml-tag"] || "";
      // Get inverse transformation rules.
      var invertedJSON;
      if (options.rules) {
          var inverseRules = fluid.model.transform.invertConfiguration(options.rules);
          // Transform back.
          invertedJSON = fluid.model.transformWithRules(json, inverseRules);
      } else {
          invertedJSON = json;
      }
      xml += xm.toxml(invertedJSON);
      // Fix indentation/newlines so it's readable.
      xml = XMLHandler.formatXml(xml);
      return xml;
  };