/*
 * SaveXML.java
 *
 * 
 * Jim Frankenberger
 * USDA-ARS, West Lafayette IN
 * jrf@purdue.edu
 *
 * Created on August 24, 2004, 3:59 PM
 */
package ex1;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileInputStream;
import de.schlichtherle.truezip.file.TFileOutputStream;
import de.schlichtherle.truezip.file.TFileReader;
import de.schlichtherle.truezip.file.TFileWriter;
import de.schlichtherle.truezip.file.TVFS;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.w3c.dom.DocumentType;
import org.xml.sax.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.util.*;

/**
 * This class handles saving crop and operation data back to XML files.
 *
 * @author jrf
 */
public class SaveXML implements EntityResolver, URIResolver {

   private final String mcrew_cfg;
   private final boolean savingCrop;
   private final WepsDBFile theFile;

   /**
    * Creates a new instance of SaveXML class
    *
    * @param topdir directory where mcrew config files are found
    * @param names all parameters read from DEFN file
    * @param wfile WepsDBFile instance of file to save
    * @param isCrop true if this is crop XML
    */
   public SaveXML(String topdir, DefnFileParser names, WepsDBFile wfile, boolean isCrop) {
      //theRow = row;
      savingCrop = isCrop;
      theFile = wfile;

      boolean slash = true;
      if (topdir.endsWith("/") || topdir.endsWith("\\")) {
         slash = true;
      }
      else {
         topdir = topdir + "/";
      }

      mcrew_cfg = (topdir);

   }

   /**
    * Called automatically by the loader to find where DTD's are located
    *
    * @param publicID
    * @param sysID
    * @return
    */
   @Override
   public InputSource resolveEntity(String publicID, String sysID) {
      TFile base = new TFile(sysID);
      String n = ("file:" + mcrew_cfg + base.getName());
      return new InputSource(n);
   }

   /**
    * Save the specified file
    *
    * @param file the name to save the data to
    * @return true if save was ok
    */
   public boolean saveFile(String file) {
      Document doc;
      boolean rc = false;

      // will save to name with a _tmp at the end so it can post-processed to remove excessive blank lines
      // which get inserted by the xml parser.
      String fileTemp = (file);
      fileTemp = fileTemp + "_tmp";

      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      try {
         DocumentBuilder builder = factory.newDocumentBuilder();
         builder.setEntityResolver(this);
         TFileInputStream in = new TFileInputStream(new TFile(file));
         doc = builder.parse(in);

         if (savingCrop) {
            modifyCropDoc(doc);
         }
         else {
            modifyOpDoc(doc);
         }
         writeXML(doc, fileTemp, file);

         TFile f = new TFile(fileTemp);
         f.rm();
         rc = true;
      }
      catch (SAXException sxe) {
         // Error generated during parsing
         Exception x = sxe;
         if (sxe.getException() != null) {
            x = sxe.getException();
         }
         x.printStackTrace();

      }
      catch (ParserConfigurationException | IOException pce) {
         // Parser with specified options can't be built
         pce.printStackTrace();
      }

      return rc;
   }

   /**
    *
    * This is the low level XML writing code for saving data in a formatted way.
    * It handles all the java bugs in dealing with XML writing.
    *
    * @param XML document to save
    * @param temporary file to save first pass xml data in
    * @param final file where results are to be placed
    */
   private boolean writeXML(Document doc, String fileTemp, String file) {
      //System.out.println("doc name: " + doc.getLocalName());
      //System.out.println("fileTemp: " + fileTemp);
      //System.out.println("file name: " + file);
      boolean rc = false;

      try {
         TransformerFactory tranFactory = TransformerFactory.newInstance();

         // the following needed to be added to make identing work with 1.5, this
         // will cause 1.4 to crash so it is inside a try block, the other outputProperties
         // below will take care of 1.4. see: http://forum.java.sun.com/thread.jspa?threadID=562510&tstart=120
         // jrf 2/22/2005
         try {
            tranFactory.setAttribute("indent-number", 0);
         }
         catch (IllegalArgumentException e) {
            // throw this away - means this is not JRE 1.5

         }
         Transformer aTransformer = tranFactory.newTransformer();

         // these aren't really needed for 1.5 but are for 1.4
         aTransformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "0");
         aTransformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "0");
         aTransformer.setOutputProperty(OutputKeys.INDENT, "yes");

         aTransformer.setURIResolver(this);

         DocumentType t = doc.getDoctype();
         if (t != null) {
            aTransformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, t.getSystemId());
         }

         Source src = new DOMSource(doc);

         // need to use stream writer to work around java bugs
         TFile destFile = new TFile(fileTemp);
         TFileOutputStream fout = new TFileOutputStream(destFile);
         try ( OutputStreamWriter destOut = new OutputStreamWriter(fout, "utf-8")) {
            Result dest = new StreamResult(destOut);

            // finally get the formatted xml output
            aTransformer.transform(src, dest);

            destOut.flush();
         }
         //Handle true zip       
         TVFS.umount();

         TFile finalFile = new TFile(file);

         // this is to work around a java bug where the transformer inserts blanks lines
         stripBlankLines(destFile, finalFile);

         //The replaceTags method will change the shortened <value/> tag for a blank entry to the more standard <value></value>
         //Uncomment the line directly below this and comment the line below that to allow the <value/> tags to stay. -DB
         //rc = true;
         rc = replaceTags(finalFile, "<value/>", "<value></value>");

      }
      catch (IOException ioe) {
         // I/O error
         ioe.printStackTrace();
      }
      catch (IllegalArgumentException | TransformerException exp) {
         //System.err.println(exp.toString());
      }

      return rc;
   }

   private boolean replaceTags(TFile finalFile, String toReplace, String replacement) {
      String pathString = finalFile.getAbsolutePath();
      try {
         Path path = Paths.get(pathString);
         //System.out.println("Generated Path: " + path.toString());
         Charset charset = StandardCharsets.UTF_8;
         String content = new String(Files.readAllBytes(path), charset);
         //System.out.println("Before Content: " + content);
         content = content.replaceAll(toReplace, replacement);
         //System.out.println("After Content: " + content);
         Files.write(path, content.getBytes(charset));
         System.out.println("Abbreviated value tags successfully completed.");
         return true;
      }
      catch (IOException | InvalidPathException e) {
         if (e instanceof InvalidPathException) {
            System.err.println("ERROR: Failed to resolve the path " + pathString);
         }
         else {
            System.err.println("ERROR: Failed to read or write file in order to fix shorthand tags!");
         }
         return false;
      }
   }

   /**
    *
    * This builds a new XML file almost from scratch. This is only needed for
    * operations files. The file is first loaded and then all the process data
    * is stripped out.
    *
    * @param file the name of the file to create
    * @return true if the file was created
    */
   public boolean createFile(String file) {
      Document doc;
      boolean rc = false;

      // send output first to a temporary file because we will strip the blank lines later.
      String fileTemp = (file);
      fileTemp = fileTemp + "_tmp";

      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      try {
         DocumentBuilder builder = factory.newDocumentBuilder();
         builder.setEntityResolver(this);
         doc = builder.parse(new TFile(file));

         removeActions(doc);

         addActions(doc);

         writeXML(doc, fileTemp, file);

         TFile f = new TFile(fileTemp);
         f.rm();

         rc = true;
      }
      catch (SAXException sxe) {
         // Error generated during parsing
         Exception x = sxe;
         if (sxe.getException() != null) {
            x = sxe.getException();
         }
         x.printStackTrace();

      }
      catch (ParserConfigurationException | IOException pce) {
         // Parser with specified options can't be built
         pce.printStackTrace();

      }

      return rc;

   }

   /*
	 * Removes any blank lines from the output XML file. This also will check for the 
	 * the header files crop_header.txt and ops_header.txt if they exist it will use
	 * the xml header information from these files instead of what the xml parser
	 * generated.
	 *
    */
   private void stripBlankLines(TFile source, TFile dest) {
      String headerFile;
      String keyword;
      if (savingCrop) {
         headerFile = mcrew_cfg + "crop_header.txt";
         keyword = "<cropDB>";
      }
      else {
         headerFile = mcrew_cfg + "ops_header.txt";
         keyword = "<operationDB>";
      }

      BufferedReader input = null;
      BufferedReader inputHeader = null;
      BufferedWriter output = null;
      Vector<String> harray = new Vector<String>(100);
      int lines = 0;
      try {
         inputHeader = new BufferedReader(new TFileReader(new TFile(headerFile)));
         String hline;
         while ((hline = inputHeader.readLine()) != null) {
            harray.add(lines++, (hline));
         }
      }
      catch (FileNotFoundException ex) {

      }
      catch (IOException ex) {

      }
      try {
         if (inputHeader != null) {
            inputHeader.close();
         }
      }
      catch (IOException ex) {
         ex.printStackTrace();
      }
      try {
         input = new BufferedReader(new TFileReader(source));
         output = new BufferedWriter(new TFileWriter(dest));
         String line = ""; //not declared within while loop
         String startStr = "";
         int idx = -1;
         if (lines > 0) {
            // need to first get past the header and then plug in the new one
            while (((line = input.readLine()) != null) && (idx == -1)) {
               idx = line.indexOf(keyword);
               if (idx > -1) {
                  // found it
                  startStr = line.substring(idx);
                  break;
               }
            }
            // write out the new header
            for (int i = 0; i < lines; i++) {
               String t = harray.get(i);
               output.write(t, 0, t.length());
               output.write("\012");
            }
            // write out the beginning tag
            output.write(startStr, 0, startStr.length());
            output.write("\012");
         }

         // write out the rest, stripping blank lines
         boolean insideValueTag = false;
         boolean firstAction = true;
         boolean firstopname = true;
         // this will handle putting in tabs, build a bunch of strings and depending
         // on the indentation level use the correct string.
         String tabs[] = new String[20];
         StringBuilder sb = new StringBuilder("\t");
         for (int i = 0; i < 20; i++) {
            tabs[i] = sb.toString();
            sb.append("\t");
         }
         int ident = 0;
         while ((line = input.readLine()) != null) {
            if (line.trim().length() > 0) {

               if (insideValueTag == false) {
                  // the following fix different formatting at the start of the file -
                  // work around java xml xalan formatting in 1.5
                  if ((firstAction) && (line.indexOf("actionvalue") > -1)) {
                     line = line.trim();
                     line = line.replaceAll("\t", "");
                     //line = "\t" + line;
                     firstAction = false;
                  }
                  if ((firstopname) && (line.indexOf("operationname") > -1)) {
                     line = line.trim();
                     line = line.replaceAll("\t", "");
                     //line = "\t" + line;
                     firstopname = false;
                  }
               }
               line = line.trim();

               // the following lines figure out the identation level we need to use
               int openbrac = countChars(line, "<");
               int closebrac = countChars(line, "</");

               openbrac = openbrac - closebrac;

               if (openbrac > closebrac) {
                  if (insideValueTag == false) {
                     line = tabs[ident] + line;
                  }
                  ident += (openbrac - closebrac);
               }
               else {
                  ident += (openbrac - closebrac);
                  if (ident >= 0) {
                     if (insideValueTag == false) {
                        line = tabs[ident] + line;
                     }
                  }
               }

               output.write(line, 0, line.length());
               output.write("\012");    // force end lines to be the same on Windows and Unix

               if (!insideValueTag) {
                  int p = 0;
                  p = line.lastIndexOf("<value>");
                  if (p >= 0) {
                     insideValueTag = true;

                     int p2 = line.lastIndexOf("</value>");
                     if (p2 > p) {
                        insideValueTag = false;
                     }
                  }
               }
               else {
                  int p2 = line.lastIndexOf("</value>");
                  if (p2 >= 0) {
                     insideValueTag = false;
                  }
                  else if (line.lastIndexOf("</>") >= 0) {
                     insideValueTag = false;
                  }
               }

            }
            else {
               // blank line could be significant if it is inside a tag
               if (insideValueTag) {
                  output.write(line, 0, line.length());
                  output.write("\012");    // force end lines to be the same on Windows and Unix
               }
            }
         }
      }
      catch (FileNotFoundException ex) {
         ex.printStackTrace();
      }
      catch (IOException ex) {
         ex.printStackTrace();
      }
      finally {
         try {
            if (input != null) {
               input.close();
            }
            if (output != null) {
               output.close();
            }
         }
         catch (IOException ex) {
            ex.printStackTrace();
         }
      }

   }

   private int countChars(String line, String val) {
      int pos = 0;
      int count = 0;
      while (pos > -1) {
         pos = line.indexOf(val, pos);
         if (pos > -1) {
            count++;
            pos = pos + val.length();
         }
      }
      return count;
   }

   /**
    * Makes a copy of a file. The actions can optionally be stripped out
    *
    * @param fsource starting file
    * @param fdest name of new file
    * @param removeActions true if all process info is stripped
    */
   public void cloneFile(String fsource, String fdest, boolean removeActions) {

      String fileTemp = (fdest);
      fileTemp = fileTemp + "_tmp";

      Document doc;

      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      try {
         DocumentBuilder builder = factory.newDocumentBuilder();
         builder.setEntityResolver(this);
         TFileInputStream in = new TFileInputStream(fsource);
         doc = builder.parse(in);

         if (removeActions) {
            removeActions(doc);
         }

         String nameTag = "cropname";
         if (savingCrop == false) {
            nameTag = "operationname";
         }

         changeName(doc, theFile.getBaseName(), nameTag);

         writeXML(doc, fileTemp, fdest);

         TFile f = new TFile(fileTemp);
         f.rm();

      }
      catch (SAXException sxe) {
         // Error generated during parsing
         Exception x = sxe;
         if (sxe.getException() != null) {
            x = sxe.getException();
         }
         x.printStackTrace();

      }
      catch (ParserConfigurationException | IOException pce) {
         // Parser with specified options can't be built
         pce.printStackTrace();
      }
   }

   /*
	 * Remove all action nodes from the document in preparation for constructing
	 * the file from scratch.
    */
   private void removeActions(Document source) {
      Element root = source.getDocumentElement();
      NodeList nl = root.getChildNodes();

      for (int i = 0; i < nl.getLength(); i++) {
         Node n = nl.item(i);
         if ((n.getNodeType() == Node.ELEMENT_NODE)
                 && (((Element) n).getTagName().equals("actionvalue"))) {
            root.removeChild(n);
         }
      }
   }

   /* 
	 * Add processes from the base file into the XML document structure.
    */
   private void addActions(Document source) {
      Element root = source.getDocumentElement();
      for (int i = 0; i < theFile.getActionCount(); i++) {
         ActionValue a = theFile.getAction(i);
         if (a != null) {
            Element act = source.createElement("actionvalue");
            Element ele = source.createElement("identity");
            Element code = source.createElement("code");
            Text t = source.createTextNode(a.getCode());
            code.appendChild(t);
            Element id = source.createElement("id");
            t = source.createTextNode(a.getID());
            id.appendChild(t);
            ele.appendChild(code);
            ele.appendChild(id);
            act.appendChild(ele);
            // Need to process all the parms for this thing
            //ArrayList c = new ArrayList();
            Collection<ParameterVal> c = a.getAllOrderedParms();
            for (ParameterVal pv : c) {
               if (pv != null) {
                  Element param = source.createElement("param");
                  Element name = source.createElement("name");
                  Element value = source.createElement("value");
                  t = source.createTextNode(pv.getActualName());
                  name.appendChild(t);
                  t = source.createTextNode(pv.getRawVal());
                  value.appendChild(t);
                  param.appendChild(name);
                  param.appendChild(value);
                  act.appendChild(param);
               }
            }
            root.appendChild(act);
         }
      }

   }

   /*
	 * This function checks for any changed parameters and sets up how to replace them in the
	 * crop DOM tree.
    */
   private boolean modifyCropDoc(Document doc) {
      // search through all the params, find any that have a changed row matching the one we are working on
      Collection<ParameterVal> c = theFile.getAllParms();
      for (ParameterVal pv : c) {
         if (pv != null) {
            if (pv.isModified()) {
               String pstr = pv.getRawVal();
               Node output = ReplaceParamValue(pv.getActualName(), doc, pstr);
               if (output == null) {
                  InsertCropParamValue(pv.getName(), doc, pstr);
               }
            }
         }
      }
      return true;
   }

   /*
	 *  This function checks for any changed parameters and sets up how to replace them in the
	 *  operatins DOM tree.
    */
   private boolean modifyOpDoc(Document doc) {
      int count = theFile.getActionCount();
      Collection<ParameterVal> c;
      for (int i = 0; i < count; i++) {
         ActionValue a = theFile.getAction(i);
         // check all parm in this action for any changes
         c = a.getAllParms();
         for (ParameterVal pv : c) {
            if (pv != null) {
               if (pv.isModified()) {
                  String pstr = pv.getRawVal();
                  String pname = pv.getActualName();
                  int len = pname.length();
                  String pnameAndId = pv.getName();
                  String id = pnameAndId.substring(len, pnameAndId.length());
                  Node output = ReplaceOpParamValue(pname, id, doc, pstr, theFile.getSub(a));
                  if (output == null) {
                     InsertOpParamValue(pname, doc, pstr);
                  }
               }
            }
         }
      }
      return true;
   }

   /*
	 *   Change the name of an operation inside the XML document
    */
   private void changeName(Document source, String newName, String nameTag) {

      Element root = source.getDocumentElement();
      NodeList nl = root.getChildNodes();

      for (int i = 0; i < nl.getLength(); i++) {
         Node n = nl.item(i);
         if ((n.getNodeType() == Node.ELEMENT_NODE)
                 && (((Element) n).getTagName().equals(nameTag))) {
            Node child = n.getChildNodes().item(0);
            do {
               if (child.getNodeType() == Node.TEXT_NODE) {
                  child.setNodeValue(newName);
                  return;
               }
               child = child.getNextSibling();
            } while (child != null);
         }
      }

   }

   /*
	 * This replaces a parameter value in an operation file where some parameter names many be
	 * duplicated. 
	 *
	 * name - parameter name
	 * id - process group the parameter falls into, used to filter out duplicate names.
	 * source - DOM tree already loaded
	 * newValue - value to store
	 * sub - specific occurance to replace, if the tree has several matching parm name and
	 *    id pairs this selects which one to use.
	 *
    */
   private Node ReplaceOpParamValue(String name, String id, Document source, String newValue, int sub) {
      Element root = source.getDocumentElement();
      NodeList nl = root.getChildNodes();
      String lastID = null;
      int curIndex = 0;

      for (int i = 0; i < nl.getLength(); i++) {
         Node n = nl.item(i);
         if ((n.getNodeType() == Node.ELEMENT_NODE)
                 && (((Element) n).getTagName().equals("actionvalue"))) {
            Node ident = ((Element) n).getElementsByTagName("identity").item(0);
            if (ident != null) {
               Node idn = ((Element) ident).getElementsByTagName("id").item(0);

               // we have an id node, now we need check if this is matches what we want.
               Node child = idn.getChildNodes().item(0);
               do {
                  if (child.getNodeType() == Node.TEXT_NODE) {
                     lastID = ((Text) child).getData();
                     ////System.out.println("Last ID: " + lastID);
                  }
                  child = child.getNextSibling();
               } while (child != null);
            }
            if (Integer.parseInt(id) == Integer.parseInt(lastID)) {
               // we are in the right action type, look at all the parms
               NodeList pnodes = ((Element) n).getChildNodes();
               for (int k = 0; k < pnodes.getLength(); k++) {
                  Node theNode = pnodes.item(k);
                  if ((theNode.getNodeType() == Node.ELEMENT_NODE)
                          && (((Element) theNode).getTagName().equals("param"))) {
                     // we have an param node, now we need to find the
                     // 'name' child
                     Node nname = ((Element) theNode).getElementsByTagName("name").item(0);

                     // there is the name, now get the text node and compare
                     Node child = nname.getChildNodes().item(0);
                     do {
                        if ((child.getNodeType() == Node.TEXT_NODE)
                                && (((Text) child).getData().equals(name))) {
                           if (curIndex == sub) {
                              // Now need to get the value node 
                              Node value = ((Element) theNode).getElementsByTagName("value").item(0);
                              if (value != null) {
                                 Node valChild = value.getChildNodes().item(0);
                                 if (valChild != null) {
                                    valChild.setNodeValue(newValue);
                                    ////System.out.println("Replaced with " + newValue);
                                    return n;
                                 }
                              }
                           }
                           else {
                              curIndex++;
                           }
                        }
                        child = child.getNextSibling();
                     } while (child != null);
                  }
               } // end for
            }
         }
      } // end for
      return null;

   }

   /*
	 * This replaces a parameter value in a crop file where all of the parameter
	 * names are unique.
	 *
	 * name - name of parameter 
	 * source - DOM tree previously loaded
	 * newValue - new value to save in the DOM tree for this parameter.
	 *
    */
   private Node ReplaceParamValue(String name, Document source, String newValue) {

      Element root = source.getDocumentElement();
      NodeList nl = root.getChildNodes();

      for (int i = 0; i < nl.getLength(); i++) {
         Node n = nl.item(i);
         if ((n.getNodeType() == Node.ELEMENT_NODE)
                 && (((Element) n).getTagName().equals("param"))) {

            // we have an param node, now we need to find the
            // 'name' child
            Node nname = ((Element) n).getElementsByTagName("name").item(0);

            // there is the name, now get the text node and compare
            Node child = nname.getChildNodes().item(0);
            do {
               if ((child.getNodeType() == Node.TEXT_NODE)
                       && (((Text) child).getData().equals(name))) {
                  // Now need to get the value node 
                  Node value = ((Element) n).getElementsByTagName("value").item(0);
                  Node valChild = value.getChildNodes().item(0);
                  valChild.setNodeValue(newValue);
                  return n;
               }
               child = child.getNextSibling();
            } while (child != null);
         }
      }
      return null;
   }

   /**
    * This inserts a parameter value in a crop file where all of the parameter
    * names are unique.
    *
    * This method was designed for the addition of developer notes: we need to
    * add the notes at the end of the file. name - name of parameter source -
    * DOM tree previously loaded newValue - new value to save in the DOM tree
    * for this parameter.
    *
    */
   private void InsertCropParamValue(String name, Document source, String newValue) {
      Element root = source.getDocumentElement();
      Element param = source.createElement("param");
      Element nodeName = source.createElement("name");
      Element nodeVal = source.createElement("value");
      nodeName.setTextContent(name);
      nodeVal.setTextContent(newValue);
      param.appendChild(nodeName);
      param.appendChild(nodeVal);
      root.appendChild(param);
   }

   /**
    * This inserts a parameter value in a op file where all of the parameter
    * names are unique.
    *
    * This method was designed for the addition of developer notes: we need to
    * add the notes at the end of the operation level action name - name of
    * parameter source - DOM tree previously loaded newValue - new value to save
    * in the DOM tree for this parameter.
    *
    */
   private void InsertOpParamValue(String name, Document source, String newValue) {
      Element root = source.getDocumentElement();
      //We need to navigate to the first actionvalue node.
      Node placement = root.getFirstChild().getNextSibling().getNextSibling().getNextSibling();
      Element param = source.createElement("param");
      Element nodeName = source.createElement("name");
      Element nodeVal = source.createElement("value");
      nodeName.setTextContent(name);
      nodeVal.setTextContent(newValue);
      param.appendChild(nodeName);
      param.appendChild(nodeVal);
      placement.appendChild(param);
   }

   /*
	 *  Called by the XML to resolve a DTD
    */
   /**
    *
    * @param href
    * @param base2
    * @return
    * @throws TransformerException
    */
   @Override
   public Source resolve(String href, String base2) throws TransformerException {
      TFile base = new TFile(href);
      String n = ("file:" + mcrew_cfg + base.getName());
      return new StreamSource(n);
   }

}
