//@include lib_Class.js

/**
 * XML Builder
 *
 * @author Michael Weiler, ELO Digital Office GmbH
 * @version 1.0
 *
 * @elojc
 * @eloas
 * @eloix
 */
sol.define("sol.common.XmlUtils", {

  singleton: true,

  /**
   * Converts a JavaScript object to a XML document
   * @param {Object} data Data object that contains the data that should be converted into XML
   * @param {Object} dataDefinition Definition object that contains meta information for the transformation.
   * @return {org.w3c.dom.Document} XML document
   *
   * ## Example
   *
   *    // Each array element of the "ChildSordDataCollector" array should be converted
   *    //   to tags with the tag name "Sord"
   *    var dataDefinition = {
   *      ChildSordDataCollector: "Sord"
   *    }
   *    var xmlDoc = sol.common.XmlUtils.convertObjectToXml({
   *      ChildSordDataCollector: [{
   *        name: "Sord1"
   *      } , {
   *        name: "Sord2"
   *      }]
   *    }, dataDefintion);
   *    XML result:
   *      <ChildSordDataCollector>
   *        <Sord>
   *          <name>Sord1</name>
   *        </Sord>#
   *        <Sord>
   *          <name>Sord2</name>
   *        </Sord>
   *      </ChildSordDataCollector>
   */
  convertObjectToXml: function (data, dataDefinition) {
    var xmlBuilder;
    xmlBuilder = sol.create("sol.common.XmlBuilder", {});
    xmlBuilder.buildFromJson(data, dataDefinition);
    return xmlBuilder.getXmlDoc();
  },

  /**
   * Returns the string representation of a XML document
   * @param {org.w3c.dom.Document} xmlDoc XML document
   * @return {String} String representation
   */
  toString: function (xmlDoc) {
    var xmlBuilder = sol.create("sol.common.XmlBuilder", { xmlDoc: xmlDoc });
    return xmlBuilder.toString();
  },

  /**
   * Reads a XML file
   * @param {String} xmlFilePath XML file path
   * @param {Object} params Parameters
   * @param {Boolean} [params.namespaceAware=false] Namespace aware
   * @return  {org.w3c.dom.Document} XML document
   */
  readXmlFile: function (xmlFilePath, params) {
    var xmlFile, documentBuilderFactory, documentBuilder, xmlDoc;
    if (!xmlFilePath) {
      throw "File path is empty";
    }

    params = params || {};

    xmlFile = new File(xmlFilePath);
    documentBuilderFactory = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance();
    if (params.namespaceAware) {
      documentBuilderFactory.namespaceAware = true;
    }
    documentBuilder = documentBuilderFactory.newDocumentBuilder();
    xmlDoc = documentBuilder.parse(xmlFile);

    return xmlDoc;
  },

  /**
   * Returns the attributes
   * @param {org.w3c.dom.Element} xmlElement XML element
   * @return {Object} Attributes object
   */
  getAttributes: function (xmlElement) {
    var attributes = {},
        namedNodeMap, i, node, nodeName, nodeValue;

    if (!xmlElement) {
      throw "XML element is empty";
    }

    namedNodeMap = xmlElement.attributes;

    if (namedNodeMap) {
      for (i = 0; i < namedNodeMap.length; i++) {
        node = namedNodeMap.item(i);
        nodeName = node.nodeName + "";
        nodeValue = node.nodeValue + "";
        attributes[nodeName] = nodeValue;
      }
    }

    return attributes;
  },

  /**
   * Returns a new xml string transformed by the given XSLT stream
   * @param {java.io.InputStream} inputXmlStream XML inputStram
   * @param {java.io.InputStream} xsltInputStream XSLT inputStream
   * @return {String} Tranformed xml
   */
  transformByXslt: function (inputXmlStream, xsltInputStream) {
    var xsltSource, transformerFactory, transformer, xmlStreamSource, outputXmlString, stringWriter;

    if (!inputXmlStream) {
      throw "'htmlInputStream' is missing";
    }
    if (!xsltInputStream) {
      throw "'xsltInputStream' is missing";
    }

    transformerFactory = javax.xml.transform.TransformerFactory.newInstance();
    xsltSource = new javax.xml.transform.stream.StreamSource(xsltInputStream);
    transformer = transformerFactory.newTransformer(xsltSource);

    xmlStreamSource = new javax.xml.transform.stream.StreamSource(inputXmlStream);
    stringWriter = new java.io.StringWriter();
    transformer.transform(xmlStreamSource, new javax.xml.transform.stream.StreamResult(stringWriter));
    outputXmlString = stringWriter.toString();
    return outputXmlString;
  }
});

/**
 * @private
 * XML builder
 */
sol.define("sol.common.XmlBuilder", {

  /**
   * @cfg {org.w3c.dom.Document}
   * XML document
   */

  initialize: function (config) {
    var me = this;

    if (config.xmlDoc) {
      me.xmlDoc = config.xmlDoc;
    }
  },

  /**
   * Creates a new XML document
   * @param {String} rootTagName Name of the root element.
   * @param {Array} attribObj Attributes of the root element.
   * @return {org.w3c.dom.Document} Created XML document.
   */
  createXml: function (rootTagName, attribObj) {
    var me = this;
    me.xmlDoc = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
    if (rootTagName) {
      me.rootElement = me.addElement(me.xmlDoc, rootTagName, "", attribObj);
      return me.rootElement;
    }
    return me.xmlDoc;
  },

  /**
   * Adds an element to the XML document
   * @param {org.w3c.dom.Node} parentElement Parent element.
   * @param {String} tagName Tag name of the new element.
   * @param {String} content Content of the new element.
   * @param {Object} attribObj Attributes of the new element as map.
   * @return {Object} newElement element to the XML document.
   */
  addElement: function (parentElement, tagName, content, attribObj) {
    var me = this,
        newElement = this.xmlDoc.createElement(tagName),
        key;
    if (attribObj) {
      for (key in attribObj) {
        newElement.setAttribute(key, attribObj[key]);
      }
    }
    if (content) {
      newElement.appendChild(me.xmlDoc.createTextNode(content));
    }
    parentElement.appendChild(newElement);
    return newElement;
  },

  /**
   * Creates a new XML document by JavaScript object
   * @param {Object} data Data object that contains the data that should be converted into XML
   * @param {Object} dataDefintion Definition object that contains meta information for the transformation.
   */
  buildFromJson: function (data, dataDefintion) {
    var me = this,
        rootTagName;
    if (!data) {
      throw "Data is missing.";
    }
    if (!dataDefintion) {
      throw "Data defintion is missing.";
    }
    rootTagName = me.getFirstPropName(data);
    if (!rootTagName) {
      throw "Root tag name is empty.";
    }
    me.dataDefinition = dataDefintion;
    me.columnSeparator = me.columnSeparator || ", ";

    me.processObj(data, me.createXml());
  },

  /**
   * @private
   * Recursive method that converts JavaScript object elements to XML
   * @param {Object} obj Data object that contains the data that should be converted into XML
   * @param {org.w3c.dom.Document} parentElement XML document.
   */
  processObj: function (obj, parentElement) {
    var me = this,
        key, newElement, arr, arrElement, arrTagName, value, i;
    for (key in obj) {
      if (typeof obj[key] === "object") {
        if (Array.isArray(obj[key])) {
          arr = obj[key];
          if (!me.dataDefinition.arrayElementTagNames) {
            throw "Array names not defined";
          }
          arrTagName = me.dataDefinition.arrayElementTagNames[key];
          if (!arrTagName) {
            throw "Array tag name for property '" + key + "' is not defined.";
          }
          arrElement = me.addElement(parentElement, key);
          for (i = 0; i < arr.length; i++) {
            newElement = me.addElement(arrElement, arrTagName);
            me.processObj(arr[i], newElement);
          }
        } else {
          newElement = me.addElement(parentElement, key);
          me.processObj(obj[key], newElement);
        }
      } else {
        value = obj[key] || "";
        value = value.replace(/ΒΆ/g, me.columnSeparator);
        me.addElement(parentElement, key, value);
      }
    }
  },

  /**
   * @private
   * Returns the first property of an object.
   * @param {Object} obj Data object that contains the data that should be converted into XML
   * @return {String} prop.
   */
  getFirstPropName: function (obj) {
    var key;
    for (key in obj) {
      return key;
    }
  },

  /**
   * Renders the XML document as string
   * @return {String} XML document as string.
   */
  toString: function () {
    var me = this,
        source, result,
        transformer = javax.xml.transform.TransformerFactory.newInstance().newTransformer();
    transformer.setOutputProperty(javax.xml.transform.OutputKeys.METHOD, "xml");
    if (!me.withDeclation) {
      transformer.setOutputProperty(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
    }
    if (me.standalone) {
      me.xmlDoc.standalone = true;
    }
    transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
    source = new javax.xml.transform.dom.DOMSource(this.xmlDoc);
    result = new javax.xml.transform.stream.StreamResult(new java.io.StringWriter());
    transformer.transform(source, result);
    return String(result.writer.toString());
  },

  /**
   * Returns the root element of the XML document.
   * @return {org.w3c.dom.Document} XML document
   */
  getXmlDoc: function () {
    var me = this;
    return me.xmlDoc;
  },

  /**
   * Returns the namespace
   * @param {org.w3c.dom.Node} node Node
   */
  getNamespaceUri: function (node) {
    var me = this,
        namespace;

    node = node || me.xmlDoc.documentElement;

    namespace = node.getNamespaceURI();

    return namespace;
  }
});