// TransPrep v 0.1
// For documentation see http://www.w3.org/2008/05/transprep/

//     W3C (copyright) SOFTWARE NOTICE AND LICENSE
//     http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231

//     This work (and included software, documentation such as READMEs, or other related items) is being provided by the copyright holders under the following license. By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions.

//     Permission to copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications:

//     1. The full text of this NOTICE in a location viewable to users of the redistributed or derivative work.
//     2. Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software Short Notice should be included (hypertext is preferred, text is permitted) within the body of any redistributed or derivative code.
//     3. Notice of any changes or modifications to the files, including the date changes were made. (We recommend you provide URIs to the location from which the code is derived.)

//     THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.

//     COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.

//     The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the software without specific, written prior permission. Title to copyright in this software and any associated documentation will at all times remain with copyright holders.

//     ____________________________________

//     This formulation of W3C's notice and license became active on December 31 2002. This version removes the copyright ownership notice such that this license can be used with materials other than those owned by the W3C, reflects that ERCIM is now a host of the W3C, includes references to this specific dated version of the license, and removes the ambiguous grant of "use". Otherwise, this version is the same as the previous version and is written so as to preserve the Free Software Foundation's assessment of GPL compatibility and OSI's certification under the Open Source Definition. Please see our Copyright FAQ for common questions about using materials from our site, including specific terms and conditions for packages like libwww, Amaya, and Jigsaw. Other questions about this notice can be directed to site-policy@w3.org.

import java.io.*;
import java.util.*;
import org.xml.sax.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;
import javax.xml.xpath.XPathFactory.*;
import javax.xml.namespace.NamespaceContext;

public class TransPrep {

    public String inputDocName;
    public String outputDoc;
    public String externalRules;
    public int outputFormat;

    private static String itsnsuri = new String("http://www.w3.org/2005/11/its");

    private static int pos;

    private static String xlinknsuri = new String(
						  "http://www.w3.org/1999/xlink");

    private static String translate = new String("translate");

    private static String yes = new String("yes");

    private static String no = new String("no");

    private static String tve = new String("itsTve");

    private static String tva = new String("itsTva");

    private static String nsxp = new String(
					    "namespace::*[not(.='http://www.w3.org/XML/1998/namespace')]");


    private static String xpathWrongMatch = new String(
						       "Warning! Match of XPath expression is different than 'element node' or 'attribute node' and will be ignored: ");

    private static String countxp = new String(
					       "count(preceding::* | ancestor::*)");

    public static void printUsage() {

	System.out.println("TransPrep v. 0.1\nFor documentation see http://www.w3.org/2008/05/transprep/\nUSAGE:\ninputfile outputfile outputformat [externalRules]\noutputformat can be one of:\n 1 = input document decorated with translation information\n 2 = pseudotranslate\n 3 = testsuite output");
    }

    private static void checkLocalMarkup(Element elem)
    {
	if (elem.hasAttributeNS(itsnsuri, translate)) {
	    if (elem.getAttributeNS(itsnsuri, translate).compareTo(yes) == 0) {
		elem.setAttribute(tve, yes);
	    }
	    if (elem.getAttributeNS(itsnsuri, translate).compareTo(no) == 0) {
		elem.setAttribute(tve, no);
	    }
	}
	else if (elem.getNamespaceURI() != null) { if (elem.getNamespaceURI().compareTo(itsnsuri) == 0 &&
						       elem.getLocalName().compareTo("span") == 0 &&
						       elem.hasAttribute("translate")) {
		String ta = elem.getAttribute("translate");
		if (ta.compareTo(yes) == 0) {
		    elem.setAttribute(tve, yes);
		}
		if (ta.compareTo(no) == 0) {
		    elem.setAttribute(tve, no);
		} 
	    }
	}
    }

    private static void traversing(Element elem) {
	checkLocalMarkup(elem);
	// Checking for inheritance and default
	String translatevalue = elem.getAttribute(tve);
	if (translatevalue.compareTo("") == 0) {
	   	Element p = (Element) elem.getParentNode();
		String translatevalueParent = p.getAttribute(tve);
		if (translatevalueParent != "") {
		    elem.setAttribute(tve, translatevalueParent);
		}
		//default below
		else {
		    elem.setAttribute(tve, yes);
		}
	}
	NodeList c = elem.getChildNodes();
	for (int i = 0; i < c.getLength(); i++) {
	    Node n = c.item(i);
	    if (n instanceof Element) {
		traversing((Element) c.item(i));
	    }
	}
	return;
    }

    private static ArrayList<Element> gatherRules(Element doc,
						  ArrayList<Element> allRules) throws Exception {
	NodeList rulesElemL = (NodeList) doc.getOwnerDocument()
	    .getElementsByTagNameNS(itsnsuri, "rules");
	for (int i = 0; i < rulesElemL.getLength(); i++) {
	    int n = i + 1;
	    Element rE = (Element) rulesElemL.item(i);
	    NodeList treL = (NodeList) rE.getElementsByTagNameNS(itsnsuri,
								 "translateRule");
	    String linkedDocN = rE.getAttributeNS(xlinknsuri, "href");
	 
	    if (linkedDocN.compareTo("") != 0) {
	    	String docUri = doc.getBaseURI();
	    	String t[] = docUri.split("[(\\)(/)]");
		String f = t[t.length-1];
		String bUri = docUri.replaceAll(f, "");
		java.net.URI linkedDocURI = new java.net.URI(bUri.concat(linkedDocN).replaceAll("file:///", ""));
		
		DocumentBuilderFactory factory = DocumentBuilderFactory
		    .newInstance();
		factory.setNamespaceAware(true);
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document document = builder.parse(new File(linkedDocURI));
		Element linkedDocRoot = document.getDocumentElement();
		gatherRules(linkedDocRoot, allRules);
	    }
	    for (int j = 0; j < treL.getLength(); j++) {
		allRules.add((Element) treL.item(j));
	    }
	}
	return allRules;
    }


    private static void processGlobalRules(Element doc, List<Element> allRules)
	throws XPathExpressionException {
	if (allRules.size() == 0) {
	} else {
	    int s = allRules.size() - 1;
	    for (int i = s; i >= 0; --i) {
		Element rule = allRules.get(i);
		MyNamespaceContext mnsc = new MyNamespaceContext();		
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		XPathExpression nsex = xpath.compile(nsxp);
		NodeList nsn = (NodeList) nsex.evaluate(rule,
							XPathConstants.NODESET);
		for (int g = 0; g < nsn.getLength(); g++) {
		    String nsuri = new String(nsn.item(g).getNodeValue());
		    String nspr = new String(rule.lookupPrefix(nsuri));
		    mnsc.setNamespace(nspr,nsuri);
		}
		String selector = rule.getAttribute("selector");
		String translateValue = rule.getAttribute("translate");
		XPath xpathtr = XPathFactory.newInstance().newXPath();
		xpathtr.setNamespaceContext(mnsc);		
		NodeList trn = (NodeList) xpathtr.evaluate(selector, doc,
							   XPathConstants.NODESET);
		for (int j = 0; j < trn.getLength(); j++) {
		    Node n = trn.item(j);
		    Short t = n.getNodeType();
		    if (t == 1) {
			Element e = (Element) n;
			if (e.getAttribute(tve) == "") {
			    e.setAttribute(tve, translateValue);
			}
		    } else if (t == 2) {
			Attr a = (Attr) n;
			Element e = a.getOwnerElement();
			if (translateValue.compareTo(yes) == 0) {
			    if (e.getAttribute(tva) == "") {
				e.setAttribute(tva, "#" + a.getName() + "#");
			    } else {
				String al = e.getAttribute(tva);
				e.setAttribute(tva, al + "#" + a.getName()
					       + "#");
			    }
			}
		    } else
			System.out.println(xpathWrongMatch + selector
					   + " Node type: " + n.getNodeName());
		    // tbd: see the "get path mail to member-i18n-its
		}
	    }
	}
    }

    public Document decorateDom ()
	throws Exception, SAXParseException, SAXException, ParserConfigurationException, IOException
    {
	Document inputDoc = parseDoc(inputDocName);
	Element root = inputDoc.getDocumentElement();
	ArrayList<Element> rulesList = new ArrayList<Element>();
	if(externalRules != null) {
	    Element erRoot = parseDoc(externalRules).getDocumentElement();
	    ArrayList<Element> allExternalRules = gatherRules(erRoot,
							      rulesList);
	    ArrayList<Element> allRules = gatherRules(root,
						      allExternalRules);	
	    processGlobalRules(root, allRules);
	} else {
	    ArrayList<Element> allRules = gatherRules(root, rulesList);
	    for(int i=0; i<allRules.size(); i++)
		{
		    //		System.out.println(allRules.get(i).getAttribute("selector") );
		}
	    processGlobalRules(root, allRules);
	}
	checkLocalMarkup(root);
	//This sets the root node to the default, if there is no local or global markup applying to it
	String translatevalue = root.getAttribute(tve);
	if (translatevalue.compareTo("") == 0){
	root.setAttribute(tve, yes);
	}
	NodeList c = root.getChildNodes();
	for (int i = 0; i < c.getLength(); i++) {
	    Node n = c.item(i);
	    Short t = n.getNodeType();
	    if (t == 1) {                
		traversing((Element)c.item(i));
	    }
	}
	return inputDoc;
    }  

    private static void pseudoTranslate(Element oDE) {
	String tva = oDE.getAttribute("itsTva");
	oDE.removeAttribute("itsTva");
	NamedNodeMap al = oDE.getAttributes();
	for (int i = 0; i < al.getLength(); i++) {
	    Attr a = (Attr) al.item(i);
	    String n = a.getName();
	    String nd = "#".concat(n).concat("#");
	    if (tva.contains(nd)) {
		a.setNodeValue("Xxxx");
	    }
	}
	NodeList c = oDE.getChildNodes();
	for (int i = 0; i < c.getLength(); i++) {
	    Node n = c.item(i);
	    if (n instanceof Text
		&& oDE.getAttribute("itsTve").compareTo("yes") == 0) {
		n.setNodeValue("Xxxx");
	    }
	    if (n instanceof Element) {
		pseudoTranslate((Element) c.item(i));
	    }
	}
	oDE.removeAttribute("itsTve");
	return;
    }


    private static String countPos(Node n, String name) {
	if (n.getNodeName().equals(name)) {
	    pos++;
	}
	if (n.getPreviousSibling() != null) {
	    Node ps = n.getPreviousSibling();
	    countPos(ps, name);
	}
	return ("" + pos);
    }

    private static String writePath(Node n) {
	String ns = ("{" + n.getNamespaceURI() + "}").replace("{null}", "{}");
	Short type = n.getNodeType();
	Node pn = n.getParentNode();
	switch (type) {
	case 1: {
	    Short pt = pn.getNodeType();
	    // This is for elements nodes.
	    switch (pt) {
		// This is used if the element node is not the root node.
	    case 1: {
		pos = 0;
		String p = "[" + countPos(n, n.getNodeName()) + "]";
		return writePath(pn) + "/" + ns + n.getLocalName() + p;
	    }
		// This is used if the element node is the root node.
	    case 9: {
		return "/" + ns + n.getLocalName();
	    }
	    }
	}
	    // This is for attribute nodes.
	case 2: {
	    Attr a = (Attr) n;
	    Node pe = a.getOwnerElement();
	    return writePath(pe) + "/@" + ns + n.getLocalName();
	}
	}
	return "";
    }

    private static void testsuite(Document tsD, Element oDE, Element tsbody)
	throws XPathExpressionException {
	XPathFactory factory = XPathFactory.newInstance();
	XPath xpath = factory.newXPath();
	XPathExpression posex = xpath.compile(countxp);
	String epos = ("e" + posex.evaluate(oDE).toString());		
	String tva = oDE.getAttribute("itsTva");
	NamedNodeMap al = oDE.getAttributes();
	for (int i = 0; i < al.getLength(); i++) {
	    Attr a = (Attr) al.item(i);
	    String n = a.getName();
	    String nd = "#".concat(n).concat("#");
	    if (n.startsWith("xmlns:") != true && n.compareTo("itsTve") != 0
		&& n.compareTo("itsTva") != 0) {
		Element node = tsD.createElement("o:node");
		node.setAttribute("path", writePath(a));
		Element output = tsD.createElement("o:output");
		if (tva.contains(nd)) {
		    output.setAttribute("o:translate", "yes");
		} else {
		    output.setAttribute("o:translate", "no");
		}
		node.appendChild(output);
		tsbody.appendChild(node);
	    }
	}
	Element node = tsD.createElement("o:node");
	node.setAttribute("path", writePath(oDE));
	Element output = tsD.createElement("o:output");
	if (oDE.getAttribute("itsTve").compareTo("yes") == 0) {
	    output.setAttribute("o:translate", "yes");
	} else {
	    output.setAttribute("o:translate", "no");
	}
	node.appendChild(output);
	tsbody.appendChild(node);
	NodeList c = oDE.getChildNodes();
	for (int i = 0; i < c.getLength(); i++) {
	    Node n = c.item(i);
	    if (n instanceof Element) {
		Element e = (Element) n;
		testsuite(tsD, e, tsbody);
	    }
	}
	return;
    }

    public void createOutput (Document doc, String docName) 
	throws TransformerConfigurationException, FileNotFoundException, TransformerException, SAXParseException, SAXException, ParserConfigurationException, IOException, XPathExpressionException
    {
	Transformer transformer = TransformerFactory.newInstance()
	    .newTransformer();
	switch(outputFormat){
	case 1: 
	    {	
		DOMSource source = new DOMSource(doc);
		FileOutputStream os = new FileOutputStream(new File(docName));
		StreamResult result = new StreamResult(os);
		transformer.transform(source, result);		
	    };	 
	    break;
	case 2:
	    { 
		pseudoTranslate(doc.getDocumentElement());
		DOMSource source = new DOMSource(doc);
		FileOutputStream os = new FileOutputStream(new File(docName));
		StreamResult result = new StreamResult(os);
		transformer.transform(source, result);
	    }
	    break;
	case 3:
	    {
		Document testSuiteDoc = parseDoc("template-testsuite.xml");
		Element testSuiteBody = (Element)testSuiteDoc.getElementsByTagName("o:nodeList")
		    .item(1);
		testsuite(testSuiteDoc, doc.getDocumentElement(), testSuiteBody);
		DOMSource source = new DOMSource(testSuiteDoc);
		FileOutputStream os = new FileOutputStream(new File(docName));
		StreamResult result = new StreamResult(os);
		transformer.transform(source, result);
	    }
	    break;
	default:
	    {
		System.out.println("Unknown output format: " + outputFormat);
	    };
	}
    }

    private Document parseDoc(String docName) 
	throws SAXParseException, SAXException, ParserConfigurationException, IOException
    {
	DocumentBuilderFactory factory = DocumentBuilderFactory
	    .newInstance();
	factory.setNamespaceAware(true);
	DocumentBuilder builder = factory.newDocumentBuilder();
	Document document = builder.parse(new File(docName));	
	return document;	
    }

    public TransPrep (String inputDocName, String outputDoc, int outputFormat) {
	this.inputDocName = inputDocName;
	this.outputDoc = outputDoc;
	this.outputFormat = outputFormat;
    }

    public TransPrep (String inputDocName, String outputDoc, int outputFormat, String externalRules) {
	this.inputDocName = inputDocName;
	this.outputDoc = outputDoc;
	this.externalRules = externalRules;
	this.outputFormat = outputFormat;
    }

    public static void main(String[] args) throws Exception {
	printUsage();
	TransPrep it = new TransPrep("inputdoc-ns.xml","output.xml",1, "externalRules.xml");
	Document decoratedDom = it.decorateDom();
	it.createOutput(decoratedDom, it.outputDoc);
    }

}