<?php

/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
  Copyright (C) 2003-2010  Cajus Pollmeier
  Copyright (C) 2011-2016  FusionDirectory

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/*!
 * \file class_xml.inc
 * Source code for class xml
 */

/*!
 * \brief This class contains all the function needed to manage xml
 * files
 */
class xml {

  /*!
   * \brief Validate a xml file from a schema
   *
   * \param string $file XML Filename
   *
   * \param string $schema Schema of the XML file
   */
  static function validate($file, $schema)
  {
    // Enable user error handling
    libxml_use_internal_errors(TRUE);

    $xml = new DOMDocument();
    $xml->load($file);

    if (!$xml->schemaValidate($schema)) {
      $errors = libxml_get_errors();
      foreach ($errors as $error) {
        $str = "";
        switch ($error->level) {
          case LIBXML_ERR_WARNING:
            $str = _("Warning")." ".$error->code.": ";
            break;
          case LIBXML_ERR_ERROR:
            $str = _("Error")." ".$error->code.": ";
            break;
          case LIBXML_ERR_FATAL:
            $str = _("Fatal error")." ".$error->code.": ";
            break;
        }
        $str .= trim($error->message);
        if ($error->file) {
            $str .= " "._("in")." ".$error->file;
        }
        $str .= " "._("on line")." ".$error->line;
        msg_dialog::display(_("XML error"), $str, ERROR_DIALOG);
      }
      libxml_clear_errors();
    }
  }

  /*!
   * \brief Transform a xml document to an array
   *
   * \param $contents Contents
   *
   * \param integer $get_attributes Initialized at 1
   *
   * \param string $priority Initialized at 'tag'
   */
  static function xml2array($contents, $get_attributes = 1, $priority = 'tag')
  {
    if (!$contents) {
      return array();
    }

    if (!function_exists('xml_parser_create')) {
      trigger_error('xml_parser_create function does not exists');
      return array();
    }

    //Get the XML parser of PHP - PHP must have this module for the parser to work
    $parser = xml_parser_create('');
    xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8"); // http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
    xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
    xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
    xml_parse_into_struct($parser, trim($contents), $xml_values);
    xml_parser_free($parser);

    if (!$xml_values) {
      return;//Hmm...
    }

    //Initializations
    $xml_array    = array();

    $current = &$xml_array; //Refference

    //Go through the tags.
    $repeated_tag_index = array();//Multiple tags with same name will be turned into an array
    foreach ($xml_values as $data) {
      unset($attributes, $value);//Remove existing values, or there will be trouble

      //This command will extract these variables into the foreach scope
      // tag(string), type(string), level(int), attributes(array).
      extract($data);//We could use the array by itself, but this cooler.

      $result = array();
      $attributes_data = array();

      if (isset($value)) {
        if ($priority == 'tag') {
          $result = $value;
        } else {
          //Put the value in a assoc array if we are in the 'Attribute' mode
          $result['value'] = $value;
        }
      }

      //Set the attributes too.
      if (isset($attributes) and $get_attributes) {
        foreach ($attributes as $attr => $val) {
          if ($priority == 'tag') {
            $attributes_data[$attr] = $val;
          } else {
            //Set all the attributes in a array called 'attr'
            $result['attr'][$attr] = $val;
          }
        }
      }

      //See tag status and do the needed.
      if ($type == "open") {//The starting of the tag '<tag>'
        $parent[$level - 1] = &$current;
        if (!is_array($current) or (!in_array($tag, array_keys($current)))) { //Insert New tag
          $current[$tag] = $result;
          if ($attributes_data) {
            $current[$tag. '_attr'] = $attributes_data;
          }
          $repeated_tag_index[$tag.'_'.$level] = 1;

          $current = &$current[$tag];

        } else { //There was another element with the same tag name

          if (isset($current[$tag][0])) {//If there is a 0th element it is already an array
            $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
            $repeated_tag_index[$tag.'_'.$level]++;
          } else {//This section will make the value an array if multiple tags with the same name appear together
            $current[$tag] = array($current[$tag],$result);//This will combine the existing item and the new item together to make an array
            $repeated_tag_index[$tag.'_'.$level] = 2;

            if (isset($current[$tag.'_attr'])) { //The attribute of the last(0th) tag must be moved as well
              $current[$tag]['0_attr'] = $current[$tag.'_attr'];
              unset($current[$tag.'_attr']);
            }
          }
          $last_item_index  = $repeated_tag_index[$tag.'_'.$level] - 1;
          $current          = &$current[$tag][$last_item_index];
        }

      } elseif ($type == "complete") { //Tags that ends in 1 line '<tag />'
        //See if the key is already taken.
        if (!isset($current[$tag])) { //New Key
          $current[$tag]                        = $result;
          $repeated_tag_index[$tag.'_'.$level]  = 1;
          if ($priority == 'tag' and $attributes_data) {
            $current[$tag. '_attr'] = $attributes_data;
          }
        } else { //If taken, put all things inside a list(array)
          if (isset($current[$tag][0]) and is_array($current[$tag])) {//If it is already an array...
            // ...push the new element into that array.
            $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;

            if ($priority == 'tag' and $get_attributes and $attributes_data) {
                $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
            }
            $repeated_tag_index[$tag.'_'.$level]++;

          } else { //If it is not an array...
            $current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
            $repeated_tag_index[$tag.'_'.$level] = 1;
            if ($priority == 'tag' and $get_attributes) {
              if (isset($current[$tag.'_attr'])) { //The attribute of the last(0th) tag must be moved as well
                $current[$tag]['0_attr'] = $current[$tag.'_attr'];
                unset($current[$tag.'_attr']);
              }

              if ($attributes_data) {
                $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
              }
            }
            $repeated_tag_index[$tag.'_'.$level]++; //0 and 1 index is already taken
          }
        }
      } elseif ($type == 'close') { //End of tag '</tag>'
        $current = &$parent[$level - 1];
      }
    }

    return $xml_array;
  }

}