Index: tmp/org.txm.concordance.core/src/org/txm/concordance/core/functions/Concordance.java
===================================================================
--- tmp/org.txm.concordance.core/src/org/txm/concordance/core/functions/Concordance.java	(revision 2995)
+++ tmp/org.txm.concordance.core/src/org/txm/concordance/core/functions/Concordance.java	(revision 2996)
@@ -207,7 +207,10 @@
 	public boolean loadParameters() {
 		
 		try {
-			
+			if (!getCorpus().hasBeenComputedOnce()) {
+				Log.warning("Error: CQP corpus is not available: " + getCorpus());
+				return false;
+			}
 			this.setQuery(this.getStringParameterValue(ConcordancePreferences.QUERY));
 			
 			this.setCQLSeparator(this.getCorpus().getCQLLimitQuery());
Index: tmp/org.txm.core/src/java/org/txm/objects/Project.java
===================================================================
--- tmp/org.txm.core/src/java/org/txm/objects/Project.java	(revision 2995)
+++ tmp/org.txm.core/src/java/org/txm/objects/Project.java	(revision 2996)
@@ -567,9 +567,9 @@
 					result.setUserPersistable(true);
 				}
 			}
-			catch (Exception e) {
-				// TODO Auto-generated catch block
-				e.printStackTrace();
+			catch (Throwable e) {
+				Log.warning("Internal error: could not initialize result: " + resultNodePath + ": " + e);
+				Log.printStackTrace(e);
 			}
 		}
 		if (errors.size() > 0) {
Index: tmp/org.txm.searchengine.cqp.core/src/org/txm/importer/cwb/ReadRegistryFile.java
===================================================================
--- tmp/org.txm.searchengine.cqp.core/src/org/txm/importer/cwb/ReadRegistryFile.java	(revision 2995)
+++ tmp/org.txm.searchengine.cqp.core/src/org/txm/importer/cwb/ReadRegistryFile.java	(revision 2996)
@@ -6,6 +6,7 @@
 import java.util.HashSet;
 
 import org.txm.utils.io.IOUtils;
+import org.txm.utils.logger.Log;
 
 /**
  * Read a registry file a retrive the declared p attributes and s attributes informations.
@@ -13,14 +14,20 @@
  * Call constructor then use : getPattributes and getSattributes for cwb-encode
  * 
  * or use getSattributesMap, getSattributeProfs and getAnatypes() to get the declared attributes
+ * 
  * @author mdecorde
  *
  */
 public class ReadRegistryFile {
+	
 	File registryFile;
+	
 	ArrayList<String> pAttributes;
+	
 	ArrayList<String> sAttributes;
+	
 	HashMap<String, HashSet<String>> sattrs;
+	
 	HashMap<String, Integer> sattrsProfs;
 	
 	public ReadRegistryFile(File registryFile) {
@@ -34,35 +41,39 @@
 	public void read() {
 		pAttributes = new ArrayList();
 		sAttributes = new ArrayList();
-		sattrs = new HashMap<String, HashSet<String>>();
-		sattrsProfs = new HashMap<String, Integer>();
+		sattrs = new HashMap<>();
+		sattrsProfs = new HashMap<>();
 		
 		for (String line : IOUtils.getLines(registryFile, System.getProperty("file.encoding"))) {
 			line = line.trim(); // remove first tab
-
+			
 			if (line.startsWith("ATTRIBUTE ")) {
 				line = line.substring(10); // remove 'ATTRIBUTE '
 				pAttributes.add(line);
-			} else if (line.startsWith("STRUCTURE ")) {
+			}
+			else if (line.startsWith("STRUCTURE ")) {
 				line = line.substring(9); // remove 'STRUCTURE '
 				line = line.replaceAll("\\#.*", "");
 				line = line.trim();
 				String[] split = line.split("_", 2);
 				String sname = split[0];
-				//println split
+				// println split
 				if (split.length == 1) { // sattr decl
-					if (sname.matches(".+[1-9]") && sattrs.containsKey(sname.substring(0, sname.length()-1))) { // recursive structure
-						sname = sname.substring(0, sname.length()-1);
-						sattrsProfs.put(sname, sattrsProfs.get(sname)+1);
-					} else {
+					if (sname.matches(".+[1-9]") && sattrs.containsKey(sname.substring(0, sname.length() - 1))) { // recursive structure
+						sname = sname.substring(0, sname.length() - 1);
+						sattrsProfs.put(sname, sattrsProfs.get(sname) + 1);
+					}
+					else {
 						sattrs.put(sname, new HashSet<String>());
 						sattrsProfs.put(sname, 0);
 					}
-				} else {
+				}
+				else {
 					String satt = split[1];
-					if (satt.matches(".+[1-9]") && sattrs.get(sname).contains(satt.substring(0, satt.length()-1))) {
+					if (satt.matches(".+[1-9]") && sattrs.get(sname).contains(satt.substring(0, satt.length() - 1))) {
 						// recursive attribute -> to be ignored
-					} else {
+					}
+					else {
 						sattrs.get(sname).add(satt);
 					}
 				}
@@ -70,16 +81,65 @@
 		}
 		
 		for (String sattr : sattrs.keySet()) {
-			String tmp = ""+sattr+":"+sattrsProfs.get(sattr);
+			String tmp = "" + sattr + ":" + sattrsProfs.get(sattr);
 			for (String attr : sattrs.get(sattr)) {
-				tmp += "+"+attr;
+				tmp += "+" + attr;
 			}
 			sAttributes.add(tmp);
 		}
 	}
 	
 	/**
+	 * Test the CQP index files of each p-attribute and s-attribute properties
 	 * 
+	 * @param dataDirectory the directory where the binary files should be found
+	 * @return true if all CQP files are present
+	 */
+	public ArrayList<String> isCorpusBuildValid(File dataDirectory) {
+		if (pAttributes == null) {
+			read();
+		}
+		
+		ArrayList<String> errors = new ArrayList<>();
+		
+		// test p-attributes
+		String[] exts = { ".corpus", ".lexicon", ".corpus.cnt", ".corpus.rdx", ".corpus.rev", ".lexicon.idx", ".lexicon.srt" };
+		for (String p : pAttributes) {
+			for (String ext : exts) {
+				File f = new File(dataDirectory, p + ext);
+				if (!f.exists()) {
+					// System.out.println("MISSING: " + f.exists() + " " + f.getAbsolutePath());
+					errors.add(f.getName());
+				}
+			}
+		}
+		
+		String[] sexts = { ".rng" };
+		String[] spexts = { ".avs", ".avx", ".rng" };
+		for (String s : sattrs.keySet()) {
+			for (String ext : sexts) {
+				File f = new File(dataDirectory, s + ext);
+				if (!f.exists()) {
+					// System.out.println("MISSING: " + f.exists() + " " + f.getAbsolutePath());
+					errors.add(f.getName());
+				}
+			}
+			
+			for (String sp : sattrs.get(s)) {
+				for (String ext : spexts) {
+					File f = new File(dataDirectory, s + "_" + sp + ext);
+					if (!f.exists()) {
+						// System.out.println("MISSING: " + f.exists() + " " + f.getAbsolutePath());
+						errors.add(f.getName());
+					}
+				}
+			}
+		}
+		return errors;
+	}
+	
+	/**
+	 * 
 	 * @return the cwb-encode arguments for p attributes
 	 */
 	public ArrayList<String> getPAttributes() {
@@ -111,10 +171,13 @@
 	}
 	
 	public static void main(String[] args) {
-		File registry = new File(System.getProperty("user.home"), "runtime-rcpapplication.product/corpora/ANNOTATIONCONC/registry/annotationconc");
+		File registry = new File(System.getProperty("user.home"), "runtime-rcpapplication.product/corpora/VOEUX/registry/voeux");
+		File data = new File(System.getProperty("user.home"), "runtime-rcpapplication.product/corpora/VOEUX/data/VOEUX");
 		ReadRegistryFile reader = new ReadRegistryFile(registry);
-		System.out.println("pAttributes: "+reader.getPAttributes());
-		System.out.println("sAttributes Map: "+reader.getSAttributesMap());
-		System.out.println("sAttributes: "+reader.getSAttributes());
+		System.out.println("pAttributes: " + reader.getPAttributes());
+		System.out.println("sAttributes Map: " + reader.getSAttributesMap());
+		System.out.println("sAttributes: " + reader.getSAttributes());
+		
+		System.out.println("Validation: " + reader.isCorpusBuildValid(data));
 	}
 }
Index: tmp/org.txm.searchengine.cqp.core/src/org/txm/searchengine/cqp/corpus/MainCorpus.java
===================================================================
--- tmp/org.txm.searchengine.cqp.core/src/org/txm/searchengine/cqp/corpus/MainCorpus.java	(revision 2995)
+++ tmp/org.txm.searchengine.cqp.core/src/org/txm/searchengine/cqp/corpus/MainCorpus.java	(revision 2996)
@@ -38,12 +38,14 @@
 import java.util.Set;
 import java.util.UUID;
 
+import org.apache.commons.lang.StringUtils;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.osgi.util.NLS;
 import org.txm.Toolbox;
 import org.txm.core.messages.TXMCoreMessages;
 import org.txm.core.results.TXMParameters;
 import org.txm.importer.cwb.PatchCwbRegistry;
+import org.txm.importer.cwb.ReadRegistryFile;
 import org.txm.objects.CorpusBuild;
 import org.txm.objects.Project;
 import org.txm.searchengine.core.messages.SearchEngineCoreMessages;
@@ -109,7 +111,7 @@
 	 * 
 	 * @param parametersNodePath the preference node path
 	 * 
-	 * @throws InvalidCqpIdException the invalid cqp id exception
+	 * @throws InvalidCqpIdException the invalid CQP id exception
 	 * @throws CqiClientException the cqi client exception
 	 */
 	public MainCorpus(String parametersNodePath) throws InvalidCqpIdException, CqiClientException {
@@ -117,11 +119,13 @@
 		
 		if (getID() != null && getProjectDirectory() != null) {
 			try {
-				compute(false);
+				if (!compute(false)) {
+					Log.warning(NLS.bind("Warning: the {0} corpus won't work correctly", this.getID()));
+					throw new IllegalStateException(NLS.bind("{0} CQP MainCorpus not instanciate correctly.", this.getID()));
+				}
 			}
 			catch (InterruptedException e) {
-				// TODO Auto-generated catch block
-				e.printStackTrace();
+				throw new IllegalStateException(e);
 			}
 		}
 	}
@@ -161,7 +165,7 @@
 			return false;
 		}
 		try {
-			try {
+			try { // fix the absolute paths in the registry file
 				PatchCwbRegistry.patch(this.registryFile, this.dataDirectory);
 			}
 			catch (IOException e) {
@@ -170,10 +174,18 @@
 				return false;
 			}
 			
+			// check if all corpus index files are present
+			ReadRegistryFile rrf = new ReadRegistryFile(this.registryFile);
+			ArrayList<String> errors = rrf.isCorpusBuildValid(this.dataDirectory);
+			if (errors.size() > 0) {
+				Log.warning(TXMCoreMessages.bind("Error: some {0} index files are missing : {1}.", this.getID(), StringUtils.join(errors, ", ")));
+				return false;
+			}
+			
 			Log.fine(NLS.bind("Call CQI: load_a_system_corpus with {0} and {1}", this.registryFile.getParent(), this.pID));
 			CQPSearchEngine.getCqiClient().load_a_system_corpus(this.registryFile.getParent(), this.pID);
 			
-			List tmp = Arrays.asList(CQPSearchEngine.getCqiClient().listCorpora());
+			List<String> tmp = Arrays.asList(CQPSearchEngine.getCqiClient().listCorpora());
 			if (tmp.contains(this.pID)) {
 				Log.fine("Corpus registered: " + pID);
 				Log.fine(NLS.bind("Call CQI: corpusProperties with {0}.", this.pID));
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/macro/prototypes/stats/InvertCAXAxisMacro.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/macro/prototypes/stats/InvertCAXAxisMacro.groovy	(revision 0)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/macro/prototypes/stats/InvertCAXAxisMacro.groovy	(revision 2996)
@@ -0,0 +1,125 @@
+// @author Sebastien Jacquot
+// STANDARD DECLARATIONS
+package org.txm.macro
+
+
+import org.kohsuke.args4j.*
+import groovy.transform.Field
+import org.txm.rcpapplication.swt.widget.parameters.*
+import org.txm.searchengine.cqp.clientExceptions.*
+import org.txm.searchengine.cqp.corpus.*
+import org.txm.searchengine.cqp.corpus.query.*
+import org.apache.commons.lang.time.StopWatch
+import java.util.Arrays
+import org.jfree.chart.renderer.xy.*
+import org.jfree.chart.renderer.*
+import org.jfree.chart.plot.*
+import org.jfree.data.xy.*
+import org.jfree.chart.axis.*
+import java.awt.*;
+import java.awt.geom.*;
+import org.jfree.chart.labels.*
+
+import org.txm.ca.core.chartsengine.jfreechart.themes.highcharts.renderers.*
+import org.txm.ca.rcp.editors.*
+import org.txm.libs.office.ReadODS
+import org.txm.ca.core.chartsengine.jfreechart.datasets.*
+import org.jfree.chart.renderer.AbstractRenderer
+
+println "editor: "+editor
+
+if (!(editor instanceof CAEditor)) {
+	println "editor is not a CA editor: $editor, Run the macro with F12 when the editor is selected :-)"
+	return
+}
+
+ica = editor.getCA();
+chart = ica.getChart();
+plot = chart.getXYPlot();
+dataset = plot.getDataset(); 
+
+// overrides some dataset methods to return inverted X coordinates for columns and rows
+plot.setDataset(new CAXYDataset(ica) {
+
+        public Number getX(int series, int item) {
+                if(item == -1)        {
+                        System.out.println("CAXYDataset.getX()");
+                }
+                // Rows
+                if(series == 0) {
+                        return -this.rowCoordinates[item][this.axis1];
+                }
+                // Cols
+                else {
+                        return -this.columnCoordinates[item][this.axis1];
+                }
+        }
+        
+        
+         
+        /**
+         * Gets the minimum value in the specified series according to the specified axis.
+         * @param series
+         * @param axis
+         * @return
+         */
+        public double getMinValue(int series, int axis)        {
+                double minValue = 0;
+                double tmpMinValue;
+                double[][] coordinates = this.rowCoordinates;
+                if(series != 0)        {
+                        coordinates = this.columnCoordinates;
+                }
+                
+                for(int i = 0; i < coordinates.length; i++) {
+                        tmpMinValue = coordinates[i][axis];
+                        
+						// invert X coordinate
+                		if(axis == 0)        {
+                			tmpMinValue = -tmpMinValue;
+                		}
+                        
+                        if(tmpMinValue < minValue)        {
+                                minValue = tmpMinValue;
+                        }
+                }
+                
+                return minValue;
+        }
+        
+        /**
+         * Gets the maximum value in the specified series according to the specified axis.
+         * @param series
+         * @param axis
+         * @return
+         */
+        public double getMaxValue(int series, int axis)        {
+                double maxValue = 0;
+                double tmpMaxValue;
+                double[][] coordinates = this.rowCoordinates;
+                if(series != 0)        {
+                        coordinates = this.columnCoordinates;
+                }
+                
+                for(int i = 0; i < coordinates.length; i++) {
+                        tmpMaxValue = coordinates[i][axis];
+                        
+						// invert X coordinate
+                		if(axis == 0)        {
+                			tmpMaxValue = -tmpMaxValue;
+                		}
+                        
+                        if(tmpMaxValue > maxValue)        {
+                                maxValue = tmpMaxValue;
+                        }
+                }
+                
+                return maxValue;
+        }
+}
+);
+
+// update the limits dotted borders
+ica.getChartCreator().createCAFactorialMapChartLimitsBorder(chart);
+
+
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/macro/transcription/Vocapia2Transcriber.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/macro/transcription/Vocapia2Transcriber.groovy	(revision 0)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/macro/transcription/Vocapia2Transcriber.groovy	(revision 2996)
@@ -0,0 +1,251 @@
+package org.txm.macro.transcription
+
+import javax.xml.stream.*
+
+import org.txm.importer.PersonalNamespaceContext
+
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.net.URL
+import java.util.regex.Pattern
+
+class Vocapia2Transcriber {
+	
+	File xmlfile;
+	protected BufferedOutputStream output;
+	protected XMLStreamWriter writer;
+	
+	public Vocapia2Transcriber(File xmlfile) {
+		
+		this.xmlfile = xmlfile;
+	}
+	
+	public boolean process(File outfile, boolean retokenizeWords) {
+		
+		if (!xmlfile.exists()) return false;
+		
+		output = new BufferedOutputStream(new FileOutputStream(outfile), 16 * 1024);
+		writer = XMLOutputFactory.newInstance().createXMLStreamWriter(output, "ISO-8859-1");// create a new file
+		writer.setNamespaceContext(new PersonalNamespaceContext());
+		
+		URL url = xmlfile.toURI().toURL();
+		String filename = outfile.getName()
+		filename = filename.substring(0, filename.length()-4); // remove ".cqp"
+		def inputData = url.openStream();
+		XMLInputFactory factory = XMLInputFactory.newInstance();
+		XMLStreamReader parser = factory.createXMLStreamReader(inputData);
+		
+		boolean flagWord = false
+		def winfos = [:]
+		boolean other = false;
+		String word = ""
+		try {
+			
+			for (int event = parser.next(); event != XMLStreamConstants.END_DOCUMENT; event = parser.next()) {
+				
+				switch (event) {
+					case XMLStreamConstants.START_ELEMENT:
+						String localname = parser.getLocalName()
+						switch(localname) {
+							case "AudioDoc": // <AudioDoc name="xyz" path="xyz.flac"> -> <Trans scribe="see Proc elements" audio_filename="xyz.flac" version="see Proc elements" version_date="see Proc elements">
+							
+								writer.writeStartDocument("ISO-8859-1", "1.0")
+							
+								writer.writeStartElement("Trans")
+								writer.writeAttribute("audio_filename", parser.getAttributeValue(null, "path"))
+								break
+							
+							case "Proc": // <Proc name="scribe" version="date" editor="AAA"/>
+							//continue writing the "Trans" element
+								if ("scribe" == parser.getAttributeValue(null, "name")) {
+									writer.writeAttribute("scribe", parser.getAttributeValue(null, "editor"))
+									writer.writeAttribute("version", parser.getAttributeValue(null, "version"))
+									writer.writeAttribute("version_date", parser.getAttributeValue(null, "version"))
+								}
+							
+								break;
+							
+							case "SpeakerList": // <SpeakerList> -> <Speakers>
+								writer.writeCharacters("\n") // after <Trans>
+								writer.writeStartElement("Speakers")
+								writer.writeCharacters("\n")
+							
+								break;
+							case "Speaker": // <Speaker ch="1" dur="531.38" gender="X" spkid="Enquêtrice" lang="fre" lconf="1.00" nw="1586" tconf="0.95"/> -> <Speaker id="spk1" name="enq4" check="no" dialect="native" accent="" scope="local"/>
+							
+								writer.writeStartElement("Speaker")
+								writer.writeAttribute("id", parser.getAttributeValue(null, "spkid"))
+								writer.writeAttribute("name", parser.getAttributeValue(null, "spkid"))
+								writer.writeAttribute("check", "")
+								writer.writeAttribute("dialect", parser.getAttributeValue(null, "lang"))
+								writer.writeAttribute("accent", parser.getAttributeValue(null, "gender"))
+								writer.writeAttribute("scope", "local")
+								writer.writeEndElement()
+								writer.writeCharacters("\n")
+								break;
+							
+							case "SegmentList":
+								writer.writeStartElement("Episode")
+							//<Section type="report" startTime="0" endTime="3617.593">
+								writer.writeStartElement("Section")
+								break;
+							
+							case "SpeechSegment": // <SpeechSegment ch="1" sconf="1.00" stime="9.94" etime="43.81" spkid="Enquêtrice" lang="fre" lconf="1.00" trs="1">
+								writer.writeStartElement("Turn")
+								writer.writeAttribute("speaker", parser.getAttributeValue(null, "spkid"))
+								writer.writeAttribute("startTime", parser.getAttributeValue(null, "stime"))
+								writer.writeAttribute("endTime", parser.getAttributeValue(null, "etime"))
+								writer.writeCharacters("\n")
+								writer.writeStartElement("Sync")
+								writer.writeAttribute("time", parser.getAttributeValue(null, "stime"))
+								writer.writeEndElement() // Sync
+								writer.writeCharacters("\n")
+								break;
+							case "Word":
+								flagWord = true
+								word = ""
+							
+							// store w infos in case the word must be splited
+								def endValue = String.format(Locale.US, "%.2f", (Double.parseDouble(parser.getAttributeValue(null, "stime")) + Double.parseDouble(parser.getAttributeValue(null, "dur"))))
+								winfos = ["time":parser.getAttributeValue(null, "stime"), "start": parser.getAttributeValue(null, "stime"), "end":endValue]
+								break
+						}
+						break;
+					case XMLStreamConstants.END_ELEMENT:
+						String localname = parser.getLocalName()
+						switch(localname) {
+							case "AudioDoc": // <AudioDoc name="xyz" path="xyz.flac"> -> <Trans scribe="see Proc elements" audio_filename="xyz.flac" version="see Proc elements" version_date="see Proc elements">
+							
+								writer.writeEndElement() // Trans
+								break
+							
+							case "Proc": // <Proc name="scribe" version="date" editor="AAA"/>
+							
+								break
+							
+							case "SpeakerList": // <SpeakerList> -> <Speakers>
+							
+								writer.writeEndElement()
+								writer.writeCharacters("\n")
+								break
+							
+							case "Speaker": // <Speaker ch="1" dur="531.38" gender="X" spkid="Enquêtrice" lang="fre" lconf="1.00" nw="1586" tconf="0.95"/> -> <Speaker id="spk1" name="enq4" check="no" dialect="native" accent="" scope="local"/>
+							//already closed
+								break
+							
+							case "SegmentList":
+								writer.writeEndElement() // Section
+								writer.writeEndElement() // Episode
+								writer.writeCharacters("\n")
+								break
+							
+							case "SpeechSegment": // <SpeechSegment ch="1" sconf="1.00" stime="9.94" etime="43.81" spkid="Enquêtrice" lang="fre" lconf="1.00" trs="1">
+								writer.writeEndElement() // Turn
+								writer.writeCharacters("\n")
+								break
+							
+							case "Word":
+								flagWord = false
+								word = word.trim()
+								if (word.startsWith("*")) {
+									other = true
+									word = word.substring(1)
+								}
+							
+								String otherAttributeValue = Boolean.toString(other) // set now
+							
+								if (other && word.endsWith("*")) {
+									word = word.substring(0, word.length()-1)
+									other = false
+								}
+							
+							// split before the word
+								def puncts = []
+								if (retokenizeWords) {
+									while (word.length() > 0 && word.matches("\\p{Punct}.+")) {
+										puncts << word.substring(0, 1)
+										word = word.substring(1, word.length())
+									}
+									
+									Pattern reg = Pattern.compile("([^']+')(.+)")
+									def m = reg.matcher(word)
+									while (word.length() > 0 && m.matches()) {
+										puncts << m.group(1)
+										word = m.group(2)
+										m = reg.matcher(word)
+									}
+								}
+								
+								for (def punct : puncts) { // pre-retokenize if any
+									writer.writeStartElement("w")
+									writer.writeAttribute("time", winfos["time"])
+									writer.writeAttribute("start", winfos["start"])
+									writer.writeAttribute("end", winfos["start"])
+									writer.writeCharacters(punct)
+									writer.writeEndElement() // w
+									writer.writeCharacters("\n")
+									
+									
+								}
+							
+								puncts = []
+								if (retokenizeWords) {
+									while (word.length() > 0 && word.matches(".+\\p{Punct}")) {
+										puncts << word.substring(word.length()-1, word.length())
+										word = word.substring(0, word.length()-1)
+									}
+								}
+							
+								writer.writeStartElement("w") // start the initial word
+								writer.writeAttribute("time", winfos["time"])
+								writer.writeAttribute("start", winfos["start"])
+								writer.writeAttribute("end", winfos["end"])
+								writer.writeAttribute("other", otherAttributeValue)
+							
+								writer.writeCharacters(word)
+								writer.writeEndElement() // w
+								writer.writeCharacters("\n")
+							
+								for (String punct : puncts) {  // post-retokenize if any
+									writer.writeStartElement("w")
+									writer.writeAttribute("time", winfos["time"])
+									writer.writeAttribute("start", winfos["end"])
+									writer.writeAttribute("end", winfos["end"])
+									writer.writeCharacters(punct)
+									writer.writeEndElement() // w
+									writer.writeCharacters("\n")
+								}
+								break
+						}
+						break
+					
+					case XMLStreamConstants.CHARACTERS:
+						if (flagWord) {
+							word += parser.getText()
+						}
+						break
+				}
+			}
+		} catch (Exception e) {
+			println "Error while processing XML File "+xmlfile+": "
+			e.printStackTrace();
+			println "At: "+parser.getLocation();
+			println "See: "+outfile.getAbsolutePath()
+		}
+		
+		output.flush()
+		writer.close()
+		output.close()
+		//writer.close()
+		parser.close()
+		//println "$xmlfile -> $outfile"
+		return true;
+	}
+	
+	public static void main(String[] args) {
+		File infile = new File("/home/mdecorde/xml/vocapia","test.xml")
+		File outfile = new File("/home/mdecorde/xml/vocapia","test.trs")
+		def processor = new Vocapia2Transcriber(infile)
+		println processor.process(outfile)
+	}
+}
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/macro/transcription/Vocapia2TranscriberMacro.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/macro/transcription/Vocapia2TranscriberMacro.groovy	(revision 0)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/macro/transcription/Vocapia2TranscriberMacro.groovy	(revision 2996)
@@ -0,0 +1,57 @@
+package org.txm.macro.transcription
+
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import org.txm.utils.*
+import org.txm.utils.logger.*
+
+@Field @Option(name="vocapiaFile", usage="A single vocapia XML file", widget="FileOpen", required=false, def="")
+		File vocapiaFile;
+
+@Field @Option(name="vocapiaDirectory", usage="A Vocapia XML files directory to process", widget="Folder", required=false, def="")
+		File vocapiaDirectory;
+
+@Field @Option(name="resultDirectory", usage="The result directory", widget="Folder", required=false, def="")
+		File resultDirectory;
+		
+@Field @Option(name="retokenize_words", usage="retokenize words prefixed or postfixed with puunctuations", widget="Boolean", required=true, def="true")
+		Boolean retokenize_words;
+
+if (!ParametersDialog.open(this)) return;
+
+resultDirectory.mkdirs();
+
+def xmlFiles = []
+if (vocapiaDirectory != null && vocapiaDirectory.exists()) {
+	
+	println "Processing directory: $vocapiaDirectory"
+	for (File file : vocapiaDirectory.listFiles()) {
+		if (file.getName().toLowerCase().endsWith(".xml")) {
+			xmlFiles << file
+		}
+	}
+} else if (vocapiaFile != null && vocapiaFile.exists()) {
+	println "Processing file: $vocapiaFile"
+	xmlFiles << vocapiaFile
+}
+
+if (xmlFiles.size() == 0) {
+	println "No XML file found for parameters vocapiaFile=$vocapiaFile and vocapiaDirectory=$vocapiaDirectory"
+	return false
+}
+
+ConsoleProgressBar cpb = new ConsoleProgressBar(xmlFiles.size())
+for (File xmlFile : xmlFiles) {
+	cpb.tick()
+	Vocapia2Transcriber v2t = new Vocapia2Transcriber(xmlFile)
+	String name = FileUtils.stripExtension(xmlFile)
+	File outFile = new File(resultDirectory, name+".trs")
+	
+	if (!v2t.process(outFile, retokenize_words)) {
+		println "WARNING: ERROR WHILE PROCESSING: "+xmlFile
+		return false
+	}
+}
+cpb.done()
+
+println "Done: "+xmlFiles.size()+" files processed. Result files in $resultDirectory"
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/transcriber/pager.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/transcriber/pager.groovy	(revision 2995)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/transcriber/pager.groovy	(revision 2996)
@@ -424,14 +424,15 @@
 									def g = l2[i]
 									metadata[m] = "" // forcing order of metadata by pre-declaring
 									
-									if (!metadataGroups.containsKey(g)) metadataGroups[g] = []
-									
+									if (!metadataGroups.containsKey(g)) {
+										metadataGroups[g] = []
+									}
 									metadataGroups[g] << m // declaring a metadata type
 								}
 								metadataDeclared = true
 							}
 						
-							//store attributes values in HashMap
+						//store attributes values in HashMap
 							for (int i = 0 ; i < parser.getAttributeCount() ; i++) {
 								String name = parser.getAttributeLocalName(i)
 								if (!"type".equals(name)
@@ -441,14 +442,14 @@
 								&& !"startTime".equals(name)
 								&& !"endTime".equals(name)) {
 									metadata[name] = parser.getAttributeValue(i)
-								
+									
 									if (!metadataDeclared && !metadataGroups["metadata"].contains(name)) {
 										metadataGroups["metadata"] << name
 									}
 								}
 							}
 						
-							// write metadata HTML
+						// write metadata HTML
 							if (metadataGroups.keySet().size() > 0) {
 								writer.writeStartElement("p")
 								writer.writeAttribute("class", "section-all-metadata");
@@ -497,12 +498,12 @@
 							writer.writeAttribute("class", "turn");
 						
 							overlapping = ("true" == parser.getAttributeValue(null,"overlap"))
-							String spid = parser.getAttributeValue(null,"speaker");
+							String spid = parser.getAttributeValue(null, "who");
 						
 							whos = []
 							if (overlapping) {
-								writer.writeEmptyElement("br");
-								writeSpeaker(parser.getAttributeValue(null,"speaker"), false)
+								//writer.writeEmptyElement("br"); // write all overlaping speakers
+								//writeSpeaker(""+parser.getAttributeValue(null, "who"), false)
 								
 								writer.writeEmptyElement("br");
 								whos = spid.split(" ")
@@ -521,16 +522,17 @@
 								writer.writeEmptyElement("br");
 							}
 						
-							String spk = parser.getAttributeValue(null, "spk")
+							String spk = parser.getAttributeValue(null, "who")
 							if (spk != null && spk != previousSPK) {
 								endBoldIfNeeded()
 								writer.writeEmptyElement("br");
-								writeSpeaker(parser.getAttributeValue(null, "spk"), overlapping)
+								writeSpeaker(parser.getAttributeValue(null, "who"), overlapping)
 								startBoldIfNeeded()
 							}
 						
 							writeCurrentTime()
 							previousSPK = spk
+							if (overlapping) previousSPK = null
 						
 						//							writenLength = 0;
 						/*writer.writeStartElement("span");
@@ -546,10 +548,12 @@
 							desc = translateEvent(desc);
 							String type = parser.getAttributeValue(null,"type");
 							if (desc.equals("paroles rapportées")) {
-								if (parser.getAttributeValue(null, "extent") == "end")
+								if (parser.getAttributeValue(null, "extent") == "end") {
 									writer.writeCharacters("» ");
-								else if (parser.getAttributeValue(null, "extent") == "begin")
+								}
+								else if (parser.getAttributeValue(null, "extent") == "begin") {
 									writer.writeCharacters(" «");
+								}
 							} else {
 								writer.writeStartElement("span");
 								writer.writeAttribute("class", "event");
@@ -564,7 +568,7 @@
 									events.add(desc)
 								}
 								else if (parser.getAttributeValue(null, "extent") == "previous") {
-									if(parser.getAttributeValue(null, "type") == "pronounce")
+									if (parser.getAttributeValue(null, "type") == "pronounce")
 										writer.writeCharacters("_["+desc+"] ");
 									else
 										writer.writeCharacters("_["+desc+"] ");
@@ -574,8 +578,9 @@
 									writer.writeCharacters(" ["+desc+"]_");
 									nextEvent = desc
 								}
-								else
+								else {
 									writer.writeCharacters(" ["+desc+"] ");
+								}
 								writer.writeEndElement(); // span@class=event
 							}
 							break;
@@ -692,13 +697,13 @@
 							if(l > 0)
 								endOfLastWord = lastword.subSequence(l-1, l);
 						
-							if(interpvalue != null)
+							if (interpvalue != null) {
 								interpvalue = interpvalue.replace("\"","&quot;");
-							if(events.size() > 0)
+							}
+							if (events.size() > 0) {
 								interpvalue = interpvalue.replace("event=", "event="+events.toString().replace("\"","&quot;")); // remove ", "
-						
-							if(nextEvent.length() > 0)
-							{
+							}
+							if (nextEvent.length() > 0) {
 								interpvalue = interpvalue.replace("event=", "event="+nextEvent+", ")
 								nextEvent = ""
 							}
@@ -709,7 +714,7 @@
 						//							println "NoSpaceAfter: "+NoSpaceAfter+" contains ? "+lastword
 						//							println "wordvalue starts with '-' ? "+wordvalue
 						//							println "NoSpaceAfter: "+NoSpaceAfter+" contains endOfLastWord ? "+endOfLastWord
-							if(NoSpaceBefore.contains(wordvalue) ||
+							if (NoSpaceBefore.contains(wordvalue) ||
 							NoSpaceAfter.contains(lastword) ||
 							wordvalue.startsWith("-") ||
 							NoSpaceAfter.contains(endOfLastWord)) {
@@ -755,12 +760,16 @@
 					break;
 				
 				case XMLStreamConstants.CHARACTERS:
-					if(flagform)
-						if(parser.getText().length() > 0)
+					if (flagform) {
+						if (parser.getText().length() > 0) {
 							wordvalue+=(parser.getText().trim());
-					if(flaginterp)
-						if(parser.getText().length() > 0)
+						}
+					}
+					if (flaginterp) {
+						if (parser.getText().length() > 0) {
 							interpvalue+=(parser.getText().trim());
+						}
+					}
 					break;
 			}
 		}
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/transcriber/TRSToTEI.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/transcriber/TRSToTEI.groovy	(revision 2995)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/transcriber/TRSToTEI.groovy	(revision 2996)
@@ -163,7 +163,7 @@
 	}
 
 	
-	boolean ignoreFirstSync = false // need to skip fist Sync when multiple locutors in Turn
+	boolean overlapingTurn = false // need to skip fist Sync when multiple locutors in Turn
 	/**
 	 * Process.
 	 *
@@ -182,21 +182,24 @@
 			case "Turn": // >> sp
 				testCloseU();
 				vSpeaker = parser.getAttributeValue(null, "speaker");
+				overlapingTurn = false
 				if (vSpeaker == null) { vSpeaker="N/A"	// no spk
 				} else {
-					localspeakers = vSpeaker.split(" ")
-					if (localspeakers.size() == 0) { // only one speaker
-						//println "FOUND ONE SPEAKER"
-						if (speakersname.containsKey(vSpeaker)) {
-							vSpeaker = speakersname.get(vSpeaker);
+					
+					if (speakersname.containsKey(vSpeaker)) {
+						//vSpeaker = speakersname.get(vSpeaker);
+					} else {
+						localspeakers = vSpeaker.split(" ")
+						if (localspeakers.size() > 1) { // only one speaker
+							overlapingTurn = true
 						}
 					}
 				}
 
 				writer.writeStartElement("sp")
 				writer.writeAttribute("n", Integer.toString(idturn++))
-				ignoreFirstSync = vSpeaker.contains(" ") // need to skip fist Sync when multiple locutors in Turn
-				writer.writeAttribute("overlap", ""+ignoreFirstSync)
+				overlapingTurn = vSpeaker.contains(" ") // need to skip fist Sync when multiple locutors in Turn
+				writer.writeAttribute("overlap", ""+overlapingTurn)
 				
 				String time = parser.getAttributeValue(null, "startTime");
 				formatedTime = formatTime(time)
@@ -217,9 +220,12 @@
 			case "Sync": // >> u
 				lastTime = parser.getAttributeValue(null, "time")
 				testCloseU();
-				if (ignoreFirstSync) { // need to skip fist Sync when multiple locutors in Turn
-					ignoreFirstSync = false;
-				} else {
+//				if (overlapingTurn) { // need to skip fist Sync when multiple locutors in Turn
+//					overlapingTurn = false;
+//				} else {
+//					
+//				}
+				if (!overlapingTurn) {
 					writeU()
 				}
 				break;
@@ -279,6 +285,7 @@
 	private testCloseU() {
 		if (uOpened) {
 			super.processEndElement(); // u
+			writer.writeCharacters("\n")
 			uOpened = false;
 		}
 	}
@@ -295,12 +302,15 @@
 //		println "getting spk name? ="+speakers.get(vSpeaker)
 //		println "speakers: $speakers"
 		def attributes = speakers.get(vSpeaker)
+		//println "ATTRIBUTES="+attributes+" vSpeaker='$vSpeaker'"
 		if (attributes == null) { // in case of Who@n wrong number
-			if (vSpeaker.startsWith("#") && vSpeaker.endsWith("?")) { // don't show "N/A" vSpeaker
+//			if (vSpeaker.startsWith("#") && vSpeaker.endsWith("?")) { // don't show "N/A" vSpeaker
 				writer.writeAttribute("who", vSpeaker)
 				writer.writeAttribute("spkid", vSpeaker)
-				writeAttributes();
-			}
+				//writeAttributes();
+//			} else {
+//				
+//			}
 		} else {
 			for (Pair p : attributes) {
 //				println " write attribute "+p.getFirst()+" "+p.getSecond()
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xtz/XTZDefaultPagerStep.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xtz/XTZDefaultPagerStep.groovy	(revision 2995)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xtz/XTZDefaultPagerStep.groovy	(revision 2996)
@@ -290,6 +290,11 @@
 	public boolean process() {
 		
 		try {
+			def anaValues = [:]
+			def anaType = ""
+			def anaResp = ""
+			def anaValue = new StringBuilder()
+			
 			boolean flagNote = false;
 			String noteContent = "";
 			String rend = ""
@@ -446,7 +451,7 @@
 								break;
 							case wordTag:
 								wordid = getAttributeValue(parser, null,"id");
-							
+								anaValues.clear()
 								wordcount++;
 								if (wordcount >= wordmax) {
 									createNextOutput();
@@ -460,11 +465,12 @@
 								break;
 							case "ana":
 								flaginterp=true;
-								interpvalue+=" "+getAttributeValue(parser, null, "type").substring(1)+":"
+								anaType = getAttributeValue(parser, null, "type").substring(1)
+								anaResp = getAttributeValue(parser, null, "resp").substring(1)
+								anaValue.setLength(0)
 								break;
 							case "form":
 								wordvalue=""
-								interpvalue =""
 								flagform=true
 								break;
 							default:
@@ -540,6 +546,9 @@
 								break;
 							case "ana":
 								flaginterp = false
+								if (anaValues[anaType] == null || "src".equals(anaResp)) {
+									anaValues[anaType] = anaValue.toString().trim()
+								}
 								break;
 							case wordTag:
 								int l = lastword.length();
@@ -548,9 +557,8 @@
 									endOfLastWord = lastword.subSequence(l-1, l)
 								}
 							
-								if (interpvalue != null) {
-									interpvalue = interpvalue
-								}
+								String interpvalue = anaValues.entrySet().join(", ")
+								
 								if (NoSpaceBefore.contains(wordvalue) ||
 								NoSpaceAfter.contains(lastword) ||
 								wordvalue.startsWith("-") ||
@@ -591,7 +599,7 @@
 								noteContent += parser.getText().replace("\n", " ")
 							}
 						} else	if (flaginterp && parser.getText().length() > 0) {
-							interpvalue+=(parser.getText())
+							anaValue.append(parser.getText())
 						} else if (flagNote == parser.getText().length() > 0) {
 							noteContent += parser.getText().replace("\n", " ")
 						} else if (writeOutOfTextToEditText) {
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xml/importer.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xml/importer.groovy	(revision 2995)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xml/importer.groovy	(revision 2996)
@@ -34,6 +34,7 @@
 
 import org.txm.*
 import org.txm.scripts.importer.*
+import org.txm.utils.io.FileCopy
 import org.txm.importer.scripts.filters.*
 import org.txm.objects.*
 import org.txm.scripts.*
@@ -144,6 +145,8 @@
 		srcDirectory.mkdir()
 		for (File f : okfiles) {
 			File outputFile = new File (srcDirectory, f.getName())
+//			println "TEMP REMOVED SURROGATE FIX"
+//			FileCopy.copy(f, outputFile)
 			CleanFile.removeSurrogateFromXmlFile(f, outputFile)
 		}
 		okfiles = srcDirectory.listFiles()
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xml/pager.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xml/pager.groovy	(revision 2995)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/scripts/importer/xml/pager.groovy	(revision 2996)
@@ -72,10 +72,7 @@
 	/** The wordvalue. */
 	String wordvalue = "";
 
-	/** The interpvalue. */
-	String interpvalue = "";
-
-	/** The lastword. */
+		/** The lastword. */
 	String lastword = " ";
 
 	/** The wordtype. */
@@ -282,6 +279,10 @@
 
 		String localname = "";
 		createNextOutput();
+		def anaValues = [:]
+		def anaType = ""
+		def anaResp = ""
+		def anaValue =  new StringBuilder()
 		for (int event = parser.next(); event != XMLStreamConstants.END_DOCUMENT; event = parser.next()) {
 			switch (event) {
 				case XMLStreamConstants.START_ELEMENT:
@@ -352,7 +353,7 @@
 						case "w":
 						
 							wordid = parser.getAttributeValue(null,"id");
-							
+							anaValues.clear()
 							wordcount++;
 							if (wordcount >= wordmax) {
 								createNextOutput();
@@ -366,11 +367,12 @@
 							break;
 						case "ana":
 							flaginterp=true;
-							interpvalue+=" "+parser.getAttributeValue(null,"type").substring(1)+":"
+							anaType = parser.getAttributeValue(null, "type").substring(1)
+							anaResp = parser.getAttributeValue(null, "resp").substring(1)
+							anaValue.setLength(0)
 							break;
 						case "form":
 							wordvalue="";
-							interpvalue ="";
 							flagform=true;
 							break;
 						//						default:
@@ -406,6 +408,9 @@
 							break;
 						case "ana":
 							flaginterp = false
+							if (anaValues[anaType] == null || "src".equals(anaResp)) {
+								anaValues[anaType] = anaValue.toString().trim()
+							}
 							break;
 						case "w":
 							int l = lastword.length();
@@ -413,9 +418,8 @@
 							if (l > 0)
 								endOfLastWord = lastword.subSequence(l-1, l);
 
-							if (interpvalue != null)
-								interpvalue = interpvalue;
-
+							String interpvalue = anaValues.entrySet().join(", ")
+							
 							if (NoSpaceBefore.contains(wordvalue) ||
 							NoSpaceAfter.contains(lastword) ||
 							wordvalue.startsWith("-") ||
@@ -441,7 +445,7 @@
 						if (flagNote == parser.getText().length() > 0)
 							noteContent += parser.getText().replace("\n", " ");
 					} else	if (flaginterp && parser.getText().length() > 0) {
-						interpvalue+=(parser.getText());
+						anaValue.append(parser.getText());
 					} else if (flagNote == parser.getText().length() > 0) {
 						noteContent += parser.getText().replace("\n", " ");
 					}
