<commit_before>/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-06 <NAME>
 *  <EMAIL>
 *  http://exist.sourceforge.net
 *  
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  
 *  $Id$
 */
package org.exist.memtree;

import java.util.Arrays;

import org.exist.dom.NodeProxy;
import org.exist.dom.QName;
import org.exist.xquery.Constants;
import org.exist.xquery.XQueryContext;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;

/**
 * Use this class to build a new in-memory DOM document.
 * 
 * @author Wolfgang <<EMAIL>>
 */
public class MemTreeBuilder {
	
	protected DocumentImpl doc;
	protected short level = 1;
	protected int[] prevNodeInLevel;
	protected XQueryContext context = null;
	
	public MemTreeBuilder() {
		this(null);
	}
	
	public MemTreeBuilder(XQueryContext context) {
		super();
		this.context = context;
		prevNodeInLevel = new int[15];
		Arrays.fill(prevNodeInLevel, -1);
		prevNodeInLevel[0] = 0;
	}

	/**
	 * Returns the created document object.
	 * 
	 */
	public DocumentImpl getDocument() {
		return doc;
	}

	public XQueryContext getContext() {
	    return context;
	}
	
	public int getSize() {
	    return doc.getSize();
	}
	
	/**
	 * Start building the document.
	 */
	public void startDocument() {
		this.doc = new DocumentImpl(context);
	}

	/**
	 * End building the document.
	 */
	public void endDocument() {
	}

	/**
	 * Create a new element.
	 * 
	 * @return the node number of the created element
	 */
	public int startElement(String namespaceURI, String localName, String qname, Attributes attributes) {
		int p = qname.indexOf(':');
		String prefix = null;
		if(context != null) {
			prefix = context.getPrefixForURI(namespaceURI);
		}
		if(prefix == null)
			prefix = (p != Constants.STRING_NOT_FOUND) ? qname.substring(0, p) : "";
        QName qn = new QName(localName, namespaceURI, prefix);
		return startElement(qn, attributes);
	}

	/**
	 * Create a new element.
	 * 
	 * @return the node number of the created element
	 */
	public int startElement(QName qn, Attributes attributes) {
		int nodeNr = doc.addNode(Node.ELEMENT_NODE, level, qn);
		if(attributes != null) {
			// parse attributes	
			for (int i = 0; i < attributes.getLength(); i++) {
                String attrNS = attributes.getURI(i);
                String attrLocalName = attributes.getLocalName(i);
                String attrQName = attributes.getQName(i);
				// skip xmlns-attributes and attributes in eXist's namespace
				if (!(attrQName.startsWith("xmlns"))) {
//					|| attrNS.equals("http://exist.sourceforge.net/NS/exist"))) {
                    int p = attrQName.indexOf(':');
                    String attrPrefix = (p != Constants.STRING_NOT_FOUND) ? attrQName.substring(0, p) : null;
                    QName attrQn = new QName(attrLocalName, attrNS, attrPrefix);
					doc.addAttribute(nodeNr, attrQn, attributes.getValue(i));
				}
			}
		}
		// update links
		if (level + 1 >= prevNodeInLevel.length) {
			int[] t = new int[level + 2];
			System.arraycopy(prevNodeInLevel, 0, t, 0, level);
			prevNodeInLevel = t;
		}
		int prevNr = prevNodeInLevel[level];
		if (prevNr > -1)
			doc.next[prevNr] = nodeNr;
		doc.next[nodeNr] = prevNodeInLevel[level - 1];
		prevNodeInLevel[level] = nodeNr;
		++level;
		return nodeNr;
	}

	/**
	 * Close the last element created.
	 */
	public void endElement() {
//		System.out.println("end-element: level = " + level);
		prevNodeInLevel[level] = -1;
		--level;
	}

	public int addReferenceNode(NodeProxy proxy) {
        int lastNode = doc.getLastNode();
        if (0 < lastNode && level == doc.getTreeLevel(lastNode)) {
            if (doc.getNodeType(lastNode) == Node.TEXT_NODE) {
                // if the last node is a text node, we have to append the
                // characters to this node. XML does not allow adjacent text nodes.
                doc.appendChars(lastNode, proxy.getNodeValue());
                return lastNode;
            }
            if (doc.getNodeType(lastNode) == NodeImpl.REFERENCE_NODE) {
                // check if the previous node is a reference node. if yes, check if it is a text node
                int p = doc.alpha[lastNode];
                if (doc.references[p].getNodeType() == Node.TEXT_NODE) {
                    // found a text node reference. create a new char sequence containing
                    // the concatenated text of both nodes
                    String s = doc.references[p].getStringValue() + proxy.getStringValue();
                    doc.replaceReferenceNode(lastNode, s);
                    return lastNode;
                }
            }
        }
        final int nodeNr = doc.addNode(NodeImpl.REFERENCE_NODE, level, null);
		doc.addReferenceNode(nodeNr, proxy);		
		linkNode(nodeNr);
		return nodeNr;
	}
	
	public int addAttribute(QName qname, String value) {
		int lastNode = doc.getLastNode();
		//if(0 < lastNode && doc.nodeKind[lastNode] != Node.ELEMENT_NODE) {
			//Definitely wrong !
			//lastNode = characters(value);
		//} else {
			//lastNode = doc.addAttribute(lastNode, qname, value);
		//}
		int nodeNr = doc.addAttribute(lastNode, qname, value);
		//TODO :
		//1) call linkNode(nodeNr); ?<commit_msg>Fixed: in an enclosed expression, an element was sometimes added as text if the previous node was a text node.<commit_after>/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-06 <NAME>
 *  <EMAIL>
 *  http://exist.sourceforge.net
 *  
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  
 *  $Id$
 */
package org.exist.memtree;

import java.util.Arrays;

import org.exist.dom.NodeProxy;
import org.exist.dom.QName;
import org.exist.xquery.Constants;
import org.exist.xquery.XQueryContext;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;

/**
 * Use this class to build a new in-memory DOM document.
 * 
 * @author Wolfgang <<EMAIL>>
 */
public class MemTreeBuilder {
	
	protected DocumentImpl doc;
	protected short level = 1;
	protected int[] prevNodeInLevel;
	protected XQueryContext context = null;
	
	public MemTreeBuilder() {
		this(null);
	}
	
	public MemTreeBuilder(XQueryContext context) {
		super();
		this.context = context;
		prevNodeInLevel = new int[15];
		Arrays.fill(prevNodeInLevel, -1);
		prevNodeInLevel[0] = 0;
	}

	/**
	 * Returns the created document object.
	 * 
	 */
	public DocumentImpl getDocument() {
		return doc;
	}

	public XQueryContext getContext() {
	    return context;
	}
	
	public int getSize() {
	    return doc.getSize();
	}
	
	/**
	 * Start building the document.
	 */
	public void startDocument() {
		this.doc = new DocumentImpl(context);
	}

	/**
	 * End building the document.
	 */
	public void endDocument() {
	}

	/**
	 * Create a new element.
	 * 
	 * @return the node number of the created element
	 */
	public int startElement(String namespaceURI, String localName, String qname, Attributes attributes) {
		int p = qname.indexOf(':');
		String prefix = null;
		if(context != null) {
			prefix = context.getPrefixForURI(namespaceURI);
		}
		if(prefix == null)
			prefix = (p != Constants.STRING_NOT_FOUND) ? qname.substring(0, p) : "";
        QName qn = new QName(localName, namespaceURI, prefix);
		return startElement(qn, attributes);
	}

	/**
	 * Create a new element.
	 * 
	 * @return the node number of the created element
	 */
	public int startElement(QName qn, Attributes attributes) {
		int nodeNr = doc.addNode(Node.ELEMENT_NODE, level, qn);
		if(attributes != null) {
			// parse attributes	
			for (int i = 0; i < attributes.getLength(); i++) {
                String attrNS = attributes.getURI(i);
                String attrLocalName = attributes.getLocalName(i);
                String attrQName = attributes.getQName(i);
				// skip xmlns-attributes and attributes in eXist's namespace
				if (!(attrQName.startsWith("xmlns"))) {
//					|| attrNS.equals("http://exist.sourceforge.net/NS/exist"))) {
                    int p = attrQName.indexOf(':');
                    String attrPrefix = (p != Constants.STRING_NOT_FOUND) ? attrQName.substring(0, p) : null;
                    QName attrQn = new QName(attrLocalName, attrNS, attrPrefix);
					doc.addAttribute(nodeNr, attrQn, attributes.getValue(i));
				}
			}
		}
		// update links
		if (level + 1 >= prevNodeInLevel.length) {
			int[] t = new int[level + 2];
			System.arraycopy(prevNodeInLevel, 0, t, 0, level);
			prevNodeInLevel = t;
		}
		int prevNr = prevNodeInLevel[level];
		if (prevNr > -1)
			doc.next[prevNr] = nodeNr;
		doc.next[nodeNr] = prevNodeInLevel[level - 1];
		prevNodeInLevel[level] = nodeNr;
		++level;
		return nodeNr;
	}

	/**
	 * Close the last element created.
	 */
	public void endElement() {
//		System.out.println("end-element: level = " + level);
		prevNodeInLevel[level] = -1;
		--level;
	}

	public int addReferenceNode(NodeProxy proxy) {
        int lastNode = doc.getLastNode();
        if (0 < lastNode && level == doc.getTreeLevel(lastNode)) {
            if (doc.getNodeType(lastNode) == Node.TEXT_NODE && proxy.getNodeType() == Node.TEXT_NODE) {
                // if the last node is a text node, we have to append the
                // characters to this node. XML does not allow adjacent text nodes.
                doc.appendChars(lastNode, proxy.getNodeValue());
                return lastNode;
            }
            if (doc.getNodeType(lastNode) == NodeImpl.REFERENCE_NODE) {
                // check if the previous node is a reference node. if yes, check if it is a text node
                int p = doc.alpha[lastNode];
                if (doc.references[p].getNodeType() == Node.TEXT_NODE && proxy.getNodeType() == Node.TEXT_NODE) {
                    // found a text node reference. create a new char sequence containing
                    // the concatenated text of both nodes
                    String s = doc.references[p].getStringValue() + proxy.getStringValue();
                    doc.replaceReferenceNode(lastNode, s);
                    return lastNode;
                }
            }
        }
        final int nodeNr = doc.addNode(NodeImpl.REFERENCE_NODE, level, null);
		doc.addReferenceNode(nodeNr, proxy);		
		linkNode(nodeNr);
		return nodeNr;
	}
	
	public int addAttribute(QName qname, String value) {
		int lastNode = doc.getLastNode();
		//if(0 < lastNode && doc.nodeKind[lastNode] != Node.ELEMENT_NODE) {
			//Definitely wrong !
			//lastNode = characters(value);
		//} else {
			//lastNode = doc.addAttribute(lastNode, qname, value);
		//}
		int nodeNr = doc.addAttribute(lastNode, qname, value);
		//TODO :
		//1) call linkNode(nodeNr); ?
{'fraction_non_alphanumeric': 0.11370311319963078, 'fraction_numerical': 0.005873961567508601, 'mean_word_length': 2.7325399310992795, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5383777', 'n_tokens_mistral': 3921, 'n_tokens_neox': 3599, 'n_words': 1087}