text: import {changeGrammarDOM, getGraph, initGraph} from "./init.js";
  import Grammar from "./grammar.js";
  import {confirmDeleteGraph, setGraphSaved, showIDs} from "./graph.js";
  import {hideErrors} from "./checkGraph.js";
  import {GRAPH_VERSION} from "./constants.js";
  
  /**
   * creates an XML representation of the graph, with mxCodec ans mxUtils.getXml
   * a version attribute is added for compatibility reasons
   *
   * @return string XML representation of the graph, grammar and version
   */
  function serializeGraph(graph) {
  
      //handle Callbacks
      if (graph.beforeSerialize) {
          for (const f of graph.beforeSerialize) {
              f();
          }
      }
  
      //use mxCodec to create an XML representations of the graph
      const enc = new mxCodec();
      const graphXml = enc.encode(graph.getModel());
  
      const rootXml = document.implementation.createDocument(null, "LRTutor", null);
      const rootElem = rootXml.getElementsByTagName('LRTutor')[0];
      rootElem.setAttribute('version', GRAPH_VERSION);
  
      const graphNode = rootXml.createElement('Graph');
      graphNode.appendChild(graphXml);
      rootElem.appendChild(graphNode);
  
      //save grammar
      const grammarNode = rootXml.createElement('Grammar');
      grammarNode.setAttribute('lr', graph.grammar.lr);
      grammarNode.setAttribute('plain', graph.grammar);
      rootElem.appendChild(grammarNode);
  
      //save start state
      graphNode.setAttribute('startState', graph.startState.id)
      graphNode.setAttribute('startSource', graph.startIndicatorSource.id)
  
      //if the graph was serialized it is considered saved.
      //it is not able to detect, if the user really saved the file.
      setGraphSaved(graph, true);
  
      return mxUtils.getPrettyXml(rootXml);
  }
  
  /**
   * Tries to parse the serial XML representation of the graph generated by <serializeGraph>
   * The correct version number must be in the input, cause they define which parts need to be there.
   * If the input is correct the current graph gets updated, or if there is no graph yet loaded, a new
   * graph will be created.
   *
   * @param serial string representation of the xml to parse
   * @return {string} optional errors when parsing the input.
   *         {undefined} if no error occurred
   */
  function deSerializeGraph(serial) {
      try {
          const doc = mxUtils.parseXml(serial);
          if (doc.getElementsByTagName("parsererror")[0]) {
              return "No XML File";
          }
          const graphNode = doc.getElementsByTagName('Graph')[0];
          const vers = doc.documentElement.getAttribute('version');
          if (vers !== GRAPH_VERSION) {
              return "Invalid File Version: '" + vers + "', need version: '" + GRAPH_VERSION + "'";
          }
  
          //add grammar
          const grammarNode = doc.getElementsByTagName('Grammar')[0];
          const grammar = new Grammar(grammarNode.getAttribute('plain'), parseInt(grammarNode.getAttribute('lr')));
          changeGrammarDOM(grammar);
          if (grammar.error())
              return "Error in the Grammar definition";
  
          if (getGraph() === undefined)
              initGraph(grammar);
  
          const graph = getGraph();
          graph.grammar = grammar;
  
          const graphDocument = mxUtils.parseXml(graphNode.innerHTML);
          const codec = new mxCodec(graphDocument);
          codec.decode(graphNode.firstElementChild, graph.getModel());
  
          //set start state
          const startID = graphNode.getAttribute('startState');
          graph.startState = graph.getModel().cells[startID];
          const startSourceID = graphNode.getAttribute('startSource');
          graph.startIndicatorSource = graph.getModel().cells[startSourceID];
          if (graph.startState === undefined || graph.startIndicatorSource === undefined) {
              return "Invalid startState or startSource. Your automaton is now in an inconsistent state";
          }
  
          //reset the connection Handler
          graph.ownConnectionHandler.abort();
          graph.ownConnectionHandler.addTerminalButtons();
  
          //show state IDs
          showIDs(graph);
          hideErrors();
          setGraphSaved(graph, true);
      } catch (e) {
          return "Error while parsing: " + e;
      }
  }
  
  /**
   * Allows different Parts of the software to register Callbacks, before a
   * the graph gets serialized.
   * Used to hide parts of the mxGraph which should not be in the save file
   * and cannot be removed after the graph is loaded again.
   *
   * @param graph mxGraph
   * @param func callback function to execute
   */
  export function executeBeforeSerialize(graph, func) {
      if (!graph.beforeSerialize) graph.beforeSerialize = [];
      graph.beforeSerialize.push(func)
  }
  
  /**
   * Creates a save file for the graph and a save dialog is opened
   */
  export function saveGraph() {
      const graph = getGraph();
      if (graph !== undefined)
          saveFile('graph.xml', serializeGraph(graph), 'text/xml');
  }
  
  /**
   * Loads the graph from a file. A File Window is opened to select the graph file
   */
  export function loadGraph() {
      const graph = getGraph();
      if (!confirmDeleteGraph(graph)) return;
      loadFile(serial => {
          // remove grammar input
          const error = deSerializeGraph(serial);
          if (error)
              alert("Failed to load the automaton: " + error);
      });
  }
  
  function saveFile(filename, data, type) {
      /**
       * Asks the user to save the data as a file to the given filename
       */
      const blob = new Blob([data], {type: type});
      if (window.navigator.msSaveOrOpenBlob) {
          // handle IE
          window.navigator.msSaveBlob(blob, filename);
      } else {
          const elem = window.document.createElement('a');
          elem.href = window.URL.createObjectURL(blob);
          elem.download = filename;
          document.body.appendChild(elem);
          elem.click();
          document.body.removeChild(elem);
          window.URL.revokeObjectURL(elem.href);
      }
  }
  
  /**
   * opens a File Window to select a file.
   * The content of the file is passed to the function to be processed.
   *
   * @param func function to process the file text.
   */
  function loadFile(func) {
      const elem = window.document.createElement('input');
      elem.type = 'file';
      elem.addEventListener('change', (e) => {
          const file = e.target.files[0];
          if (!file) {
              return;
          }
          const reader = new FileReader();
          reader.onload = function (e) {
              const content = e.target.result;
              func(content);
          };
          reader.readAsText(file);
      });
      document.body.appendChild(elem);
      elem.click();
      document.body.removeChild(elem);
  }
  