Index: tmp/org.txm.utils/src/org/txm/utils/io/IOUtils.java
===================================================================
--- tmp/org.txm.utils/src/org/txm/utils/io/IOUtils.java	(revision 2023)
+++ tmp/org.txm.utils/src/org/txm/utils/io/IOUtils.java	(revision 2024)
@@ -26,21 +26,21 @@
 import org.txm.utils.i18n.DetectBOM;
 
 public class IOUtils {
-	
+
 	public static final String UTF8 = "UTF-8"; //$NON-NLS-1$
 
 	public static ArrayList<String> findWithGroup(File file, String pattern) throws IOException {
 		return findWithGroup(file, UTF8, pattern, false);
 	}
-	
+
 	public static ArrayList<String> findWithGroup(File file, String pattern, boolean normalizeSeparators) throws IOException {
 		return findWithGroup(file, UTF8, pattern, normalizeSeparators);
 	}
-	
+
 	public static ArrayList<String> findWithGroup(File file, String encoding, String pattern) throws IOException {
 		return findWithGroup(file, encoding, pattern, false);
 	}
-	
+
 	/**
 	 * select only files not hidden
 	 */
@@ -50,7 +50,7 @@
 			return !file.isHidden() && !file.getName().startsWith(".") && !file.getName().endsWith("~");
 		}
 	};
-	
+
 	/**
 	 * 
 	 * @return a list containing the files not hidden
@@ -58,10 +58,10 @@
 	public List<File> listFiles(File dir) {
 		File[] files = dir.listFiles(HIDDENFILE_FILTER);
 		if (files == null) return new ArrayList<File>();
-		
+
 		return Arrays.asList(files);
 	}
-	
+
 	public static ArrayList<String> findWithGroup(File file, String encoding, String pattern, boolean normalizeSeparators) throws IOException {
 		ArrayList<String> matches = new ArrayList<String>();
 		String text = IOUtils.getText(file, encoding);
@@ -71,7 +71,7 @@
 			text = text.replaceAll("\\n", " "); //$NON-NLS-1$
 			text = text.replaceAll("[ ]+", " "); //$NON-NLS-1$
 		}
-		
+
 		Pattern test = Pattern.compile(pattern);
 		Matcher m = test.matcher(text);
 		while (m.find()) {
@@ -79,19 +79,19 @@
 		}
 		return matches;
 	}
-	
+
 	public static ArrayList<String> find(File file, String pattern) throws IOException {
 		return find(file, UTF8, pattern, false);
 	}
-	
+
 	public static ArrayList<String> find(File file, String pattern, boolean normalizeSeparators) throws IOException {
 		return findWithGroup(file, UTF8, pattern, normalizeSeparators);
 	}
-	
+
 	public static ArrayList<String> find(File file, String encoding, String pattern) throws IOException {
 		return find(file, encoding, pattern, false);
 	}
-	
+
 	public static ArrayList<String> find(File file, String encoding, String pattern, boolean normalizeSeparators) throws IOException {
 		ArrayList<String> matches = new ArrayList<String>();
 		String text = IOUtils.getText(file, encoding);
@@ -102,7 +102,7 @@
 			text = text.replaceAll("\\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
 			text = text.replaceAll("[ ]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
 		}
-		
+
 		Pattern test = Pattern.compile(pattern);
 		Matcher m = test.matcher(text);
 		while (m.find()) {
@@ -110,7 +110,7 @@
 		}
 		return matches;
 	}
-	
+
 	public static BufferedReader getReader(File file) throws UnsupportedEncodingException, FileNotFoundException {
 		return getReader(file, UTF8);
 	}
@@ -130,7 +130,7 @@
 	public static BufferedReader getReader(String file) throws UnsupportedEncodingException, FileNotFoundException {
 		return getReader(new File(file), UTF8);
 	}
-	
+
 	public static BufferedReader getReader(URL url) throws IOException {
 		return getReader(url, UTF8);
 	}
@@ -145,7 +145,7 @@
 		writer.print(text);
 		writer.close();
 	}
-	
+
 	public static String getText(URL url, String encoding) throws IOException {
 		BufferedReader reader = getReader(url, encoding);
 		StringBuilder builder = new StringBuilder();
@@ -161,7 +161,7 @@
 		}
 		return builder.toString();
 	}
-	
+
 	public static String getText(File xmlWFile, String string) throws IOException {
 		BufferedReader reader = getReader(xmlWFile);
 		StringBuilder builder = new StringBuilder();
@@ -235,7 +235,7 @@
 	public static OutputStream getOutputStream(File file) throws FileNotFoundException {
 		return new BufferedOutputStream(new FileOutputStream(file, false));
 	}
-	
+
 	public static OutputStream getOutputStream(File file, boolean append) throws FileNotFoundException {
 		return new BufferedOutputStream(new FileOutputStream(file, append));
 	}
@@ -245,10 +245,21 @@
 		text = text.replace(oldString, newString);
 		IOUtils.write(file, text);
 	}
-	
+
 	public static void replaceAll(File file, Pattern regex, String newString) throws IOException {
 		String text = getText(file, UTF8);
 		text = regex.matcher(text).replaceAll(newString);
 		IOUtils.write(file, text);
 	}
+
+	public static String firstLine(File file)  {
+		try {
+			BufferedReader reader = getReader(file);
+			String line = reader.readLine();
+			reader.close();
+			return line;
+		} catch(Exception e) {
+			return null;
+		}
+	}
 }
Index: tmp/org.txm.groovy.core/src/java/GroovyPreferences.java
===================================================================
--- tmp/org.txm.groovy.core/src/java/GroovyPreferences.java	(revision 2023)
+++ tmp/org.txm.groovy.core/src/java/GroovyPreferences.java	(revision 2024)
@@ -1,12 +0,0 @@
-import org.txm.core.preferences.TXMPreferences;
-
-public class GroovyPreferences extends TXMPreferences {
-	
-	public static final String PREFERENCES_NODE = GroovyPreferences.class.getName();//FrameworkUtil.getBundle(RPreferences.class).getSymbolicName();
-
-	@Override
-	public void initializeDefaultPreferences() {
-		super.initializeDefaultPreferences();
-		// TODO Auto-generated method stub
-	}
-}
Index: tmp/org.txm.groovy.core/src/java/ExecJS.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/java/ExecJS.groovy	(revision 2023)
+++ tmp/org.txm.groovy.core/src/java/ExecJS.groovy	(revision 2024)
@@ -1,47 +0,0 @@
-import org.txm.edition.rcp.editors.*
-/*
-script = """sheet.insertRule("#w_a01_390 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_470 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_262 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_289 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_421 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_213 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_166 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_342 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_546 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_589 {background-color:rgba(249,208,208,1.0);}", 1);
-sheet.insertRule("#w_a01_504 {background-color:rgba(249,160,160,1.0);}", 1);
-sheet.insertRule("#w_a01_637 {background-color:rgba(249,208,208,1.0);}", 1);
-*/
-script = """
-
-//alert(sheet.cssRules[0].selectorText);
-//alert(sheet.cssRules[0].style.cssText);
-//alert(sheet.cssRules[0].style.length);
-//alert(sheet.cssRules[0].style.parentRule);
-
-for (var i=0; i < document.styleSheets.length; i++){
-	var styleSheet = document.styleSheets[i];
-	alert(styleSheet.cssRules.length)
-/*	for (var j=0; j < styleSheet.cssRules.length; j++){
-		alert(styleSheet.cssRules[j].style.cssText)
-	}
-*/
-}
-//alert("SHEET="+sheet.cssRules.length)
-//document.head.appendChild(style);
-"""
-/*
-
- */
-if (!(editor.getClass().getName().equals(")org.txm.edition.rcp.editors.SynopticEditionEditor"))) {
-	println "Editor is not an edition: "+editor+". Use the F12 key binding to run the macro."
-	return
-}
-monitor.syncExec(new Runnable() {
-	public void run() {
-		panel = editor.editionPanels["default"]
-		
-		println "Execute result: "+panel.execute(script)
-}});
-
Index: tmp/org.txm.groovy.core/src/java/org/txm/groovy/core/InstallGroovyFiles.java
===================================================================
--- tmp/org.txm.groovy.core/src/java/org/txm/groovy/core/InstallGroovyFiles.java	(revision 2023)
+++ tmp/org.txm.groovy.core/src/java/org/txm/groovy/core/InstallGroovyFiles.java	(revision 2024)
@@ -3,11 +3,13 @@
 import java.io.File;
 import java.io.IOException;
 
+import org.osgi.framework.Version;
 import org.txm.PostTXMHOMEInstallationStep;
 import org.txm.Toolbox;
 import org.txm.objects.Workspace;
 import org.txm.utils.BundleUtils;
 import org.txm.utils.io.FileCopy;
+import org.txm.utils.logger.Log;
 
 /**
  * Copy all Groovy scripts in TXMHome directory
@@ -23,16 +25,30 @@
 	public boolean do_install(Workspace workspace) {
 		File txmhomedir = new File(Toolbox.getTxmHomePath());
 		
+		String version = GroovyPreferences.getInstance().getString(GroovyPreferences.VERSION);
 		
+		boolean doInstall = false;
+		Version installedVersion = BundleUtils.getBundleVersion("org.txm.groovy.core");
+		if (version != null && version.length() > 0) {
+			Version v = new Version(version);
+			
+			doInstall = v.compareTo(installedVersion) > 0;
+			Log.fine("Groovy installed version: "+installedVersion+" preferenced version: "+v+" -> "+doInstall);
+		} else {
+			doInstall = true;
+			GroovyPreferences.getInstance().put(GroovyPreferences.VERSION, installedVersion.toString());
+		}
+		
+		if (!doInstall) return true; // nothing to do
+		
 		for (String folder : createfolders) {
 			new File(txmhomedir, folder).mkdirs();
 		}
 		File scriptsDirectory = new File(txmhomedir, "scripts/groovy");
 		File userDirectory = new File(scriptsDirectory, "user");
 		File systemDirectory = new File(scriptsDirectory, "system");
+		Log.fine("Installing Groovy files to "+userDirectory);
 		
-		
-		
 //		HashMap<String, String> publishedImports = new HashMap<String, String>();
 //		String IMPORTERPACKAGE="org/txm/scriptsimporter/";
 //		publishedImports.put(IMPORTERPACKAGE+"alceste","alcesteLoader.groovy");
@@ -51,7 +67,6 @@
 //		publishedImports.put(IMPORTERPACKAGE+"doc","docLoader.groovy");
 //		publishedImports.put(IMPORTERPACKAGE+"cqp","cqpLoader.groovy");
 //		
-//		
 //		for (String p : publishedImports.keySet()) {
 //			BundleUtils.copyFiles(bundle_id, "src/", "groovy/"+p, publishedImports.get(p), importDirectory);
 //		}
@@ -77,13 +92,12 @@
 		
 		try {
 			FileCopy.copyFiles(userDirectory, systemDirectory);
+			Log.fine("Copying user Groovy files to "+systemDirectory);
 		} catch (IOException e) {
 			// TODO Auto-generated catch block
 			e.printStackTrace();
 		}
 		
-
-		
 		return scriptsDirectory.exists();
 	}
 
Index: tmp/org.txm.groovy.core/src/java/org/txm/groovy/core/GroovyPreferences.java
===================================================================
--- tmp/org.txm.groovy.core/src/java/org/txm/groovy/core/GroovyPreferences.java	(revision 2023)
+++ tmp/org.txm.groovy.core/src/java/org/txm/groovy/core/GroovyPreferences.java	(revision 2024)
@@ -5,9 +5,21 @@
 	
 	public static final String PREFERENCES_NODE = GroovyPreferences.class.getName();//FrameworkUtil.getBundle(RPreferences.class).getSymbolicName();
 
+	public static final String VERSION = "version";
+	
+	/**
+	 * Gets the instance.
+	 * @return the instance
+	 */
+	public static TXMPreferences getInstance()	{
+		if (!TXMPreferences.instances.containsKey(GroovyPreferences.class)) {
+			new GroovyPreferences();
+		}
+		return TXMPreferences.instances.get(GroovyPreferences.class);
+	}
+	
 	@Override
 	public void initializeDefaultPreferences() {
 		super.initializeDefaultPreferences();
-		// TODO Auto-generated method stub
 	}
 }
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/macro/office/MergeExcelDirMacro.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/macro/office/MergeExcelDirMacro.groovy	(revision 0)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/macro/office/MergeExcelDirMacro.groovy	(revision 2024)
@@ -0,0 +1,135 @@
+package org.txm.macro.office
+
+import org.kohsuke.args4j.*
+import groovy.transform.Field
+import java.nio.charset.Charset
+import org.txm.rcpapplication.swt.widget.parameters.*
+import org.txm.utils.*
+import javax.xml.stream.*
+import java.net.URL
+
+import org.apache.poi.ss.usermodel.*
+import org.apache.poi.hssf.usermodel.*
+import org.apache.poi.xssf.usermodel.*
+import org.apache.poi.ss.util.*
+
+@Field @Option(name="inputDirectory", usage="répertoire des fichiers Excel à traiter", widget="Folder", required=true, def="")
+		def inputDirectory
+
+@Field @Option(name="outputFile", usage="output file", widget="File", required=true, def="")
+		def outputFile
+
+@Field @Option(name="sheetName", usage="sheet name (if no name is given the first sheet will be used)", widget="String", required=false, def="")
+		def sheetName
+
+@Field @Option(name="columnList", usage="column list to extract, separated with comma", widget="String", required=false, def="")
+		def columnList
+
+if (!ParametersDialog.open(this)) return
+
+	if (!inputDirectory.exists()) {
+		println "** $scriptName: no '"+inputDirectory.name+"' directory found. Aborting."
+		return false
+	}
+
+if (!inputDirectory.canRead()) {
+	println "** $scriptName: '"+inputDirectory.name+"' directory not readable. Aborting."
+	return false
+}
+
+def scriptName = this.class.getSimpleName()
+
+columnList = columnList.split("\t")
+
+println "columnList = "+columnList
+
+// create output file
+out = new FileOutputStream(outputFile)
+HSSFWorkbook wb = new HSSFWorkbook()
+finalSheet = wb.createSheet()
+
+// create header line
+int nFinalRows = 1
+r = finalSheet.createRow(0)
+columnList.eachWithIndex { columnName, i ->
+	c = r.createCell(i)
+	c.setCellValue(columnName)
+}
+
+def f = []
+
+inputDirectory.eachFileMatch(~/.*xlsx/) { f << it }
+
+if (f.size() == 0) {
+	println "** $scriptName: no .xlsx file found. Aborting."
+	return false
+}
+debug = false;
+ConsoleProgressBar cpb = new ConsoleProgressBar(f.size())
+try {
+
+	f.sort { it.name }.each { inputFile ->
+
+		if (debug) println "Processing "+inputFile+"."
+		cpb.tick()
+		rownum = 0
+
+		// open input stylesheet
+		Workbook wb2 = WorkbookFactory.create(inputFile)
+		Sheet ws = null;
+		if (sheetName.length() == 0) {
+			ws = wb2.getSheetAt(0)
+		} else {
+			ws = wb2.getSheet(sheetName)
+			if (ws == null) {
+				println "** Excel2XML: no '"+sheetName+" found. Aborting."
+				return false
+			}
+		}
+
+		if (ws == null) {
+			println "** $scriptName: no sheet found. Aborting."
+			return false
+		}
+
+		nRows = ws.getPhysicalNumberOfRows()
+		if (debug) println nRows+" rows."
+
+		Row firstRow = ws.getRow(0) // get header line
+		int nCells = firstRow.getPhysicalNumberOfCells()
+		def cellIndexes = []
+		for (int iCell = 0 ; iCell < nCells ; iCell++) { // get indexes of columns to copy
+			String value = firstRow.getCell(iCell).getStringCellValue()
+			if (columnList.contains(value)) {
+				cellIndexes << iCell
+			}
+		}
+		
+		for (int iRow = 1 ; iRow < nRows ; iRow++) { // copy rows from next lines
+			Row row = ws.getRow(iRow)
+			r = finalSheet.createRow(nFinalRows)
+			
+			int nCellCreated = 0
+			for (int iCell : cellIndexes) {
+				String value = row.getCell(iCell).getStringCellValue()
+				Cell c = r.createCell(nCellCreated)
+				c.setCellValue(value)
+				nCellCreated++
+			}
+			
+			nFinalRows++
+		}
+	}
+} catch(Exception e) {
+	return false;
+}
+cpb.done();
+
+/* write sheet */
+
+wb.write(out)
+out.close()
+
+println nFinalRows+" rows written."
+
+return true
Index: tmp/org.txm.libs.msoffice/build.properties
===================================================================
--- tmp/org.txm.libs.msoffice/build.properties	(revision 2023)
+++ tmp/org.txm.libs.msoffice/build.properties	(revision 2024)
@@ -1,5 +1,18 @@
 source.. = src/
 output.. = bin/
 bin.includes = META-INF/,\
-               .
+               .,\
+               lib/commons-codec-1.10.jar,\
+               lib/commons-collections4-4.1.jar,\
+               lib/commons-logging-1.2.jar,\
+               lib/curvesapi-1.04.jar,\
+               lib/junit-4.12.jar,\
+               lib/log4j-1.2.17.jar,\
+               lib/ooxml-schemas-1.3.jar,\
+               lib/poi-3.17.jar,\
+               lib/poi-excelant-3.17.jar,\
+               lib/poi-ooxml-3.17.jar,\
+               lib/poi-ooxml-schemas-3.17.jar,\
+               lib/poi-scratchpad-3.17.jar,\
+               lib/xmlbeans-2.6.0.jar
 qualifier=svn
Index: tmp/org.txm.libs.msoffice/META-INF/MANIFEST.MF
===================================================================
--- tmp/org.txm.libs.msoffice/META-INF/MANIFEST.MF	(revision 2023)
+++ tmp/org.txm.libs.msoffice/META-INF/MANIFEST.MF	(revision 2024)
@@ -5,3 +5,17 @@
 Bundle-Version: 1.0.0.qualifier
 Automatic-Module-Name: org.txm.libs.msoffice
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-ClassPath: lib/commons-codec-1.10.jar,
+ lib/commons-collections4-4.1.jar,
+ lib/commons-logging-1.2.jar,
+ lib/curvesapi-1.04.jar,
+ lib/junit-4.12.jar,
+ lib/log4j-1.2.17.jar,
+ lib/ooxml-schemas-1.3.jar,
+ lib/poi-3.17.jar,
+ lib/poi-excelant-3.17.jar,
+ lib/poi-ooxml-3.17.jar,
+ lib/poi-ooxml-schemas-3.17.jar,
+ lib/poi-scratchpad-3.17.jar,
+ lib/xmlbeans-2.6.0.jar,
+ .
Index: tmp/org.txm.rcp/src/main/java/org/txm/rcp/views/fileexplorer/MacroContentProvider.java
===================================================================
--- tmp/org.txm.rcp/src/main/java/org/txm/rcp/views/fileexplorer/MacroContentProvider.java	(revision 2023)
+++ tmp/org.txm.rcp/src/main/java/org/txm/rcp/views/fileexplorer/MacroContentProvider.java	(revision 2024)
@@ -30,7 +30,9 @@
 import java.io.File;
 import java.io.FileFilter;
 
+import org.txm.utils.io.IOUtils;
 
+
 // TODO: Auto-generated Javadoc
 /**
  * The Class FileTreeContentProvider.
@@ -53,11 +55,18 @@
 		Object[] kids = ((File) element).listFiles(new FileFilter() {	
 			@Override
 			public boolean accept(File file) {
+				if (file.isHidden()) return false;
 				if (file.isDirectory()) return true;
-				if (file.isHidden()) return false;
+				
 				if (macroExplorer.isShowAll()) return true;
+				if (!file.getName().endsWith("Macro.groovy")) return false; //$NON-NLS-1$
 				
-				return file.getName().endsWith("Macro.groovy"); //$NON-NLS-1$
+				String line = IOUtils.firstLine(file);
+				if (line != null) {
+					line = line.trim();
+					return !(line.contains("@STATUS=BETA") || line.contains("@STATUS=ALPHA"));
+				}
+				return true;
 			}
 		});
 		
