Index: tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/IndentMacro.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/IndentMacro.groovy	(revision 0)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/IndentMacro.groovy	(revision 1384)
@@ -0,0 +1,133 @@
+package org.txm.macro.xml
+// STANDARD DECLARATIONS
+
+import org.kohsuke.args4j.*
+import groovy.transform.Field
+import org.txm.rcpapplication.swt.widget.parameters.*
+import org.txm.importer.ApplyXsl2
+
+// BEGINNING OF PARAMETERS
+
+@Field @Option(name="inputDirectory", usage="an example folder", widget="Folder", required=true, def="in")
+def inputDirectory = new File(System.getProperty("user.home"),"/tmp/xsltest")
+
+@Field @Option(name="outputDirectory", usage="an example folder", widget="Folder", required=true, def="out")
+def outputDirectory = new File("/tmp/xsltest/out")
+
+@Field @Option(name="omitXmlDeclaration", usage="omit xml declaration", widget="Boolean", required=false, def="false")
+def omitXmlDeclaration
+
+@Field @Option(name="suppressIndentation", usage="space delimited list of elements not to indent", widget="Text", required=false, def="")
+def suppressIndentation
+
+@Field @Option(name="doubleSpace", usage="space delimited list of elements with forced line break before", widget="Text", required=false, def="")
+def doubleSpace
+
+@Field @Option(name="encoding", usage="output character encoding", widget="String", required=false, def="utf-8")
+def encoding
+
+@Field @Option(name="namespacesDeclaration", usage="space delimited list of namespace declarations (eg \"xmlns:dc='http://purl.org/dc/elements/1.1/'\")", widget="Text", required=false, def="")
+def namespacesDeclaration
+
+@Field @Option(name="debug", usage="Show debug messages", widget="Boolean", required=true, def="false")
+def debug
+
+if (!ParametersDialog.open(this)) return
+
+// END OF PARAMETERS
+
+doubleSpace = doubleSpace.trim()
+
+if (doubleSpace.length() > 0) doubleSpace = " saxon:double-space=\"$doubleSpace\""
+
+suppressIndentation = suppressIndentation.trim()
+
+if (suppressIndentation.length() > 0) suppressIndentation = " saxon:suppress-indentation=\"$suppressIndentation\""
+
+encoding = encoding.trim()
+
+if (encoding.length() > 0) encoding = " encoding=\"$encoding\""
+
+if (omitXmlDeclaration) omitXmlDeclarationP = " omit-xml-declaration=\"yes\"" else omitXmlDeclarationP = " omit-xml-declaration=\"no\""
+
+namespacesDeclaration = namespacesDeclaration.trim()
+
+if (namespacesDeclaration.length() > 0) namespacesDeclaration = " $namespacesDeclaration"
+
+def xslParameters = ""
+
+IndentStyleSheet = """
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:saxon="http://saxon.sf.net/"${namespacesDeclaration}>
+  <xsl:output method="xml" indent="yes"${omitXmlDeclarationP}${encoding}${doubleSpace}${suppressIndentation}/>
+	<!-- saxon:indent-spaces="6" for Saxon-PE only -->
+  <xsl:template match="node()|@*">
+    <xsl:copy>
+      <!-- Including any attributes it has and any child nodes -->
+      <xsl:apply-templates select="@*|node()"/>
+    </xsl:copy>
+  </xsl:template>
+</xsl:stylesheet>"""
+
+def XSLFile = File.createTempFile("IndentStyleSheet",".xsl")
+XSLFile.deleteOnExit()
+
+XSLFile << IndentStyleSheet
+
+outputDirectory.mkdir()
+
+xslParameters = xslParameters.trim()
+if (xslParameters.length() == 0) {
+	xslParameters = null
+} else {
+	def split = xslParameters.split("\n")
+	xslParameters = []
+	for (def str : split) {
+		def split2 = str.split("=", 2)
+		if (split2.size() == 2) {
+			xslParameters << split2
+		}
+	}
+}
+
+println "Processed directory: $inputDirectory."
+
+def files = [] 
+ApplyXsl2 a = new ApplyXsl2(XSLFile.getAbsolutePath())
+inputDirectory.eachFileMatch(~/.+\.(xml|XML)/) { XMLFile ->
+	String name = XMLFile.getName()
+	try {
+		
+		def outFile = new File(outputDirectory, name)
+		if (xslParameters != null) {
+			for(def param : xslParameters) {
+				a.setParam(param[0], param[1])
+			}
+		}
+		a.process(XMLFile, outFile)
+		a.resetParams()
+		files << XMLFile
+	} catch (Exception e) {
+
+			msgParse = (e.getMessageAndLocation() =~ /^(.*) lineNumber: ([0-9]+); columnNumber: ([0-9]+); (.*).$/)
+			if (msgParse.size() == 1) {
+
+				def lineNumber = msgParse[0][2]
+				def colNumber = msgParse[0][3]
+				def errMsg = msgParse[0][4]
+
+				println "** $name processing aborted, [line $lineNumber, character $colNumber]: $errMsg."
+
+
+			} else {
+				println e.getMessageAndLocation()
+			}
+
+		XSLFile.delete()
+		return 0
+	}
+}
+
+XSLFile.delete()
+
+return 1
+
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/ValidateMacro.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/ValidateMacro.groovy	(revision 0)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/ValidateMacro.groovy	(revision 1384)
@@ -0,0 +1,206 @@
+// Copyright © 2018 ENS de Lyon, CNRS, University of Franche-Comté
+// Licensed under the terms of the GNU General Public License (http://www.gnu.org/licenses)
+// @author sheiden
+
+package org.txm.macro.xml
+
+// STANDARD DECLARATIONS
+
+import java.nio.file.Files
+
+import org.kohsuke.args4j.*
+import groovy.transform.Field
+import org.txm.rcpapplication.commands.*
+import org.txm.rcpapplication.swt.widget.parameters.*
+import org.txm.importer.ApplyXsl2
+
+import net.sf.saxon.TransformerFactoryImpl
+import org.txm.utils.saxon.SaxonNodeSet
+import javax.xml.transform.stream.StreamSource
+import javax.xml.transform.stream.StreamResult
+
+import org.eclipse.core.filesystem.EFS
+import org.eclipse.ui.PlatformUI
+import org.eclipse.ui.ide.IDE
+import org.eclipse.jface.text.ITextOperationTarget
+
+// BEGINNING OF PARAMETERS
+
+@Field @Option(name="inputDirectory", usage="an example folder", widget="Folder", required=true, def="in")
+def inputDirectory = new File(System.getProperty("user.home"),"/tmp/xsltest")
+
+@Field @Option(name="edit", usage="edit XML files with errors", widget="Boolean", required=true, def="true")
+def edit
+
+@Field @Option(name="maxEditors", usage="maximum number of files opened", widget="Integer", required=true, def="4")
+def maxEditors
+
+@Field @Option(name="debug", usage="Show debug messages", widget="Boolean", required=true, def="false")
+def debug
+
+if (!ParametersDialog.open(this)) return
+
+// END OF PARAMETERS
+
+def xslParameters = ""
+def nEditors = 0
+
+IdentityStyleSheet = """
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:saxon="http://saxon.sf.net/">
+  <xsl:output method="xml"/>
+  <xsl:template match="node()|@*">
+    <xsl:copy>
+      <xsl:apply-templates select="@*|node()"/>
+    </xsl:copy>
+  </xsl:template>
+</xsl:stylesheet>"""
+
+def XSLFile = File.createTempFile("IdentityStyleSheet",".xsl")
+XSLFile.deleteOnExit()
+addShutdownHook {
+    XSLFile.delete()
+    println XSLFile+" deleted."
+}
+
+XSLFile << IdentityStyleSheet
+
+def outputDirectory = Files.createTempDirectory("Validate")
+outputDirectory = outputDirectory.toFile()
+outputDirectory.deleteOnExit()
+
+xslParameters = xslParameters.trim()
+if (xslParameters.length() == 0) {
+	xslParameters = null
+} else {
+	def split = xslParameters.split("\n")
+	xslParameters = []
+	for (def str : split) {
+		def split2 = str.split("=", 2)
+		if (split2.size() == 2) {
+			xslParameters << split2
+		}
+	}
+}
+
+println "Processed directory: $inputDirectory."
+
+// let's build a Saxon XSLT processor
+
+System.setProperty("javax.xml.transform.TransformerFactory", "net.sf.saxon.TransformerFactoryImpl")
+tFactory = new net.sf.saxon.TransformerFactoryImpl()
+tConfiguration = tFactory.getConfiguration()
+tConfiguration.registerExtensionFunction(new SaxonNodeSet()) // ?
+// the following features don't seem to work, yet
+tConfiguration.setConfigurationProperty(net.sf.saxon.lib.FeatureKeys.LINE_NUMBERING, true)
+tConfiguration.setConfigurationProperty(net.sf.saxon.lib.FeatureKeys.RECOVERY_POLICY, net.sf.saxon.Configuration.RECOVER_WITH_WARNINGS)
+
+// === TODO: other configuration tuning options ===
+// Basically:
+// - schema validation
+// - dtd validation
+// - etc.
+//
+// DTD_VALIDATION
+// DTD_VALIDATION_RECOVERABLE
+// LINE_NUMBERING
+// RECOVERY_POLICY: Configuration.RECOVER_SILENTLY, Configuration.RECOVER_WITH_WARNINGS, or Configuration.DO_NOT_RECOVER.
+// RECOVERY_POLICY_NAME (get?)
+// SCHEMA_VALIDATION
+// SCHEMA_VALIDATION_MODE: None, Strict, Lax, Preserve
+// STANDARD_ERROR_OUTPUT_FILE
+// STRIP_WHITESPACE
+// SUPPRESS_XSLT_NAMESPACE_CHECK
+// TRACE_LISTENER
+// VALIDATION_COMMENTS
+// VALIDATION_WARNINGS
+// XSD_VERSION
+// XSLT_SCHEMA_AWARE
+
+def transformer = tFactory.newTransformer(new StreamSource(XSLFile))
+
+inputDirectory.eachFileMatch(~/.+\.(xml|XML)/) { xmlInputFile ->
+	name = xmlInputFile.getName()
+	def editor = ""
+	try {
+		
+		def xmlOutputFile = new File(outputDirectory, name)
+		xmlOutputFile.deleteOnExit()
+
+		try {
+
+		// redirect XSLT processor error output
+		tmperr = new ByteArrayOutputStream()
+		tFactory.getErrorListener().setErrorOutput(new PrintStream(tmperr))
+
+                strWriter = new StringWriter()
+                out = new StreamResult(strWriter)
+
+		transformer.transform(new StreamSource(xmlInputFile), out)
+
+		if (debug) println "stderr = "+tmperr.toString(java.nio.charset.StandardCharsets.UTF_8)
+		//System.setErr(stderr)
+
+		w = new BufferedWriter(new FileWriter(xmlOutputFile))
+		w.write(strWriter.toString())
+                w.close()
+
+		// reset transformer to free memory
+		transformer = tFactory.newTransformer(new StreamSource(XSLFile))
+
+		} catch (Exception e) {
+
+			// org.xml.sax.SAXParseException; systemId: file:/home/sheiden/Documents/txm/xml/sample.xml; lineNumber: 3; columnNumber: 1; Le type d'élément "a" doit être suivi des spécifications d'attribut, ">" ou "/>".
+
+			msgParse = (e.getMessageAndLocation() =~ /^(.*) lineNumber: ([0-9]+); columnNumber: ([0-9]+); (.*).$/)
+			if (msgParse.size() == 1) {
+
+				def lineNumber = msgParse[0][2]
+				def colNumber = msgParse[0][3]
+				def errMsg = msgParse[0][4]
+
+				println "** $name [line $lineNumber, character $colNumber]: $errMsg."
+
+				if (edit && (nEditors < maxEditors++)) {
+					def fileStore = EFS.getLocalFileSystem().getStore(xmlInputFile.toURI())
+					monitor.syncExec(new Runnable() {
+						public void run() {
+    							def page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
+							editor = IDE.openEditorOnFileStore(page, fileStore)
+							if (editor.getClass() == org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorPart) {
+								editor = editor.fTextEditor
+							}
+							def dprov = editor.getDocumentProvider()
+							def doc = dprov.getDocument(editor.getEditorInput())
+							def nlines = doc.getNumberOfLines()
+							def line = Integer.parseInt(lineNumber)-1
+							if (nlines < line) line = nlines-1
+							def ncols = doc.getLineLength(line)
+							def col = Integer.parseInt(colNumber)-1
+							if (ncols <= col) col = ncols-1
+							def offset = doc.getLineOffset(line)+col
+							def textv = editor.getAdapter(ITextOperationTarget.class)
+							textv.getTextWidget().setSelection(offset)
+							textv.revealRange(offset, 10)
+						} // run
+					    } // Runnable
+					) // syncExec
+				}
+			} else {
+				println e.getMessageAndLocation()
+			}
+
+		}
+	} catch (Exception ee) {
+		//println "Warning: XSL transformation of '$name' failed with error=$e"
+		//if (debug) " with: "+e.printStackTrace()
+		XSLFile.delete()
+		outputDirectory.deleteDir()
+		return 0
+	}
+}
+
+XSLFile.delete()
+outputDirectory.deleteDir()
+
+return 1
+
Index: tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/CombineNumberElementMacro.groovy
===================================================================
--- tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/CombineNumberElementMacro.groovy	(revision 0)
+++ tmp/org.txm.groovy.core/src/groovy/org/txm/macro/xml/CombineNumberElementMacro.groovy	(revision 1384)
@@ -0,0 +1,47 @@
+// Copyright © 2018 ENS de Lyon, CNRS, University of Franche-Comté
+// Licensed under the terms of the GNU General Public License (http://www.gnu.org/licenses)
+// @author sheiden
+
+/*
+
+This macro calls NumberElement successively
+
+*/
+
+package org.txm.macro.xml
+
+res = gse.run(NumberElementMacro, [
+	"args":[
+		"inputDirectory":new File("/home/sheiden/Documents/projet-susana-mars-reers/corpus/tei"),
+		"outputDirectory":new File("/home/sheiden/Documents/projet-susana-mars-reers/corpus/tei/out"),
+		"elementName":"pb",
+		"elementAttribute":"n",
+		"countStart":94,
+		"valuePrefix":"",
+		"valueSuffix":"",
+		"debug":"true"
+	],
+				"selection":selection,
+				"selections":selections,
+				"corpusViewSelection":corpusViewSelection,
+				"corpusViewSelections":corpusViewSelections,
+				"monitor":monitor])
+if (!res) println "** problem calling NumberElementMacro."
+
+res = gse.run(NumberElementMacro, [
+	"args":["inputDirectory":new File("/home/sheiden/Documents/projet-susana-mars-reers/corpus/tei/out"),
+	"outputDirectory":new File("/home/sheiden/Documents/projet-susana-mars-reers/corpus/tei/out/out"),
+	"elementName":"pb",
+	"elementAttribute":"facs",
+	"countStart":102,
+	"valuePrefix":"https://gallica.bnf.fr/iiif/ark:/12148/bpt6k35936/f",
+	"valueSuffix":"/full/full/0/native.jpg",
+	"debug":"true"
+	],
+				"selection":selection,
+				"selections":selections,
+				"corpusViewSelection":corpusViewSelection,
+				"corpusViewSelections":corpusViewSelections,
+				"monitor":monitor])
+if (!res) println "** problem calling NumberElementMacro."
+
