Index: TXM/trunk/org.txm.groovy.core/src/groovy/org/txm/macro/cqp/StructIndexMacro.groovy
===================================================================
--- TXM/trunk/org.txm.groovy.core/src/groovy/org/txm/macro/cqp/StructIndexMacro.groovy	(revision 0)
+++ TXM/trunk/org.txm.groovy.core/src/groovy/org/txm/macro/cqp/StructIndexMacro.groovy	(revision 3590)
@@ -0,0 +1,85 @@
+// Copyright © 2023 ENS de Lyon
+// Licensed under the terms of the GNU General Public License version 3 (http://www.gnu.org/licenses/gpl-3.0.html)
+// @author sheiden
+
+// STANDARD DECLARATIONS
+package org.txm.macro.cqp
+
+import org.kohsuke.args4j.*
+import groovy.transform.Field
+import org.txm.rcp.swt.widget.parameters.*
+import org.txm.macro.cqp.CQPUtils
+import org.txm.searchengine.cqp.CQPSearchEngine
+
+scriptName = this.class.getSimpleName()
+
+// BEGINNING OF PARAMETERS
+
+@Field @Option(name="struct_props", usage="struct@prop1,prop2...", widget="String", required=true, def="")
+def struct_props
+
+@Field @Option(name="Vmax", usage="maximum number of values displayed", widget="Integer", required=true, def="100")
+def Vmax
+
+@Field @Option(name="groupByValue", usage="group results by value before counting", widget="Boolean", required=false, def="true")
+def groupByValue
+
+// Open the parameters input dialog box
+if (!ParametersDialog.open(this)) return
+
+// END OF PARAMETERS
+
+utils = new CQPUtils()
+corpusEngine = CQPSearchEngine.getCqiClient()
+
+// check for a corpus selection
+corpora = utils.getCorpora(this)
+
+if ((corpora == null) || corpora.size() > 1) {
+	println "** $scriptName: please select a corpus in the Corpus view or provide a corpus name. Aborting."
+	return false
+}
+
+if ((struct_props == null) || struct_props.size() == 0) {
+	println "** $scriptName: please set the 'struct_props' parameter. Aborting."
+	return false
+}
+
+corpus = corpora[0].getMainCorpus().getName()
+
+globalVmax = Vmax
+
+(struct, prop) = struct_props.tokenize('@')*.trim()
+	
+  if ((prop == null) || prop.size() == 0) {
+    println "** $scriptName: please set @properties in the 'struct_props' parameter."
+  } else {
+
+    if (prop.indexOf(',') == -1) {
+      prop = [prop]
+    } else {
+      prop = prop.tokenize(',')*.trim()
+    }
+
+    occs = (0..corpusEngine.attributeSize("$corpus.${struct}_${prop[0]}")-1).collect { cpos ->
+      prop.collect { prop ->
+        corpusEngine.struc2Str("$corpus.${struct}_$prop", [cpos] as int[])[0]
+      }.join('_')
+    }
+	
+    if (groupByValue) {
+      occs = occs.sort().countBy { it }.sort { a,b -> b.value <=> a.value ?: a.key <=> b.key }
+      if (globalVmax == 0) {
+      	occs.each { println sprintf("%s\t%d", it.key, it.value) }
+      } else {
+      occs.take(globalVmax).each { println sprintf("%s\t%d", it.key, it.value) }
+      }
+    } else {
+      if (globalVmax == 0) {
+        occs.each { println it }
+      } else {
+        occs.take(globalVmax).each { println it }
+      }
+    }
+  }
+
Index: TXM/trunk/org.txm.groovy.core/src/groovy/org/txm/macro/cqp/StructsIndexMacro.groovy
===================================================================
--- TXM/trunk/org.txm.groovy.core/src/groovy/org/txm/macro/cqp/StructsIndexMacro.groovy	(revision 0)
+++ TXM/trunk/org.txm.groovy.core/src/groovy/org/txm/macro/cqp/StructsIndexMacro.groovy	(revision 3590)
@@ -0,0 +1,112 @@
+// Copyright © 2023 ENS de Lyon
+// Licensed under the terms of the GNU General Public License version 3 (http://www.gnu.org/licenses/gpl-3.0.html)
+// @author sheiden
+
+// STANDARD DECLARATIONS
+package org.txm.macro.cqp
+
+import org.kohsuke.args4j.*
+import groovy.transform.Field
+import org.txm.rcp.swt.widget.parameters.*
+import org.txm.macro.cqp.CQPUtils
+import org.txm.searchengine.cqp.CQPSearchEngine
+
+scriptName = this. class .getSimpleName()
+
+// BEGINNING OF PARAMETERS
+
+@Field @Option(name = "structs_props", usage = "struct1@prop1_prop2,struct2@prop3_prop4...", widget 
+    = "String", required = true, def  = "")
+def structs_props
+
+@Field @Option(name = "Vmax", usage = "maximum number of values displayed (use '0' to display all)", widget = "Integer", required = true, 
+    def  = "0")
+def Vmax
+
+// Open the parameters input dialog box
+if (!ParametersDialog.open(this)) {
+    return
+}
+
+// END OF PARAMETERS
+
+utils = new CQPUtils()
+corpusEngine = CQPSearchEngine.getCqiClient()
+
+// check for a corpus selection
+corpora = utils.getCorpora(this)
+
+if ((corpora == null) || corpora.size() > 1) {
+    println "** $scriptName: please select a corpus in the Corpus view or provide a corpus name. Aborting."
+    return false
+}
+
+// check for structs_props parameter
+if ((structs_props == null) || structs_props.size() == 0) {
+    println "** $scriptName: please set the 'structs_props' parameter. Aborting."
+    return false
+}
+
+corpus_name = corpora[0].getMainCorpus().getName()
+corpus_size = corpora[0].getMainCorpus().getSize()
+
+if (structs_props.indexOf(',') == -1) {
+    struct_props = [structs_props]
+} else {
+    struct_props = structs_props.tokenize(',')*.trim()
+}
+
+globalVmax = Vmax
+
+def printPropValues(start, end, struct_prop, output) {
+
+    if (struct_prop.size() > 0) {
+    
+        curr_struct_prop = struct_prop[0]
+        s_p = curr_struct_prop.tokenize('@')*.trim()
+        def struct = s_p[0]
+        def prop = s_p[1]
+        
+        if ((prop == null) || prop.size() == 0) {
+            println "** $scriptName: please set @properties in the 'structs_props' parameter."
+        } else {
+        
+            if (prop.indexOf('_') == -1) {
+                prop = [prop]
+            } else {
+                prop = prop.tokenize('_')*.trim()
+            }
+            
+            occs = (0..corpusEngine.attributeSize("$corpus_name.${struct}_${prop[0]}")-1).collect {
+                idx -> curr_start = corpusEngine.struc2Cpos("$corpus_name.${struct}_${prop[0]}", idx as int)[0]
+                curr_end = corpusEngine.struc2Cpos("$corpus_name.${struct}_${prop[0]}", idx as int)[1]
+                if (curr_start >= start && curr_end <= end) {
+                    [
+                        curr_start,
+                        curr_end,
+                        prop.collect {
+                            curr_prop -> corpusEngine.struc2Str("$corpus_name.${struct}_$curr_prop", [idx] as int [])[0]
+                        }.join('_')
+                    ]
+                } else null
+            }
+            def n = 0
+            occs.each {
+                if (it != null && (globalVmax == 0 || n < globalVmax)) {
+                    (start, end, value) = it
+                    // println prefix+value // struct+"@"+prop[0]+"="+
+                    if (output.length() > 0) {
+                        printPropValues(start, end, struct_prop.tail(), output + "\t" + value)
+                    } else {
+                        printPropValues(start, end, struct_prop.tail(), value)
+                    }
+                    n++
+                }
+            }
+        }
+    } else println output
+}
+
+printPropValues(0, corpus_size, struct_props, "")
+
+
