Revision: 20797
          http://sourceforge.net/p/jmol/code/20797
Author:   hansonr
Date:     2015-09-28 10:02:39 +0000 (Mon, 28 Sep 2015)
Log Message:
-----------
Jmol.___JmolVersion="14.3.16_2015.09.28"

new feature: array.sort("key")
 -- sorts an array of associative arrays by the specified associative array key.
 -- example:
 
        load sf6.smol
        x= _M.moData.mos.select("energy,index where energy<0")
        x.sort("energy").reverse
        print x

          {
            "energy"  :  -0.67001194
            "index"  :  35
           }
          {
            "energy"  :  -0.67001194
            "index"  :  34
           }
          {
            "energy"  :  -0.67001194
            "index"  :  33
           }
          {
            "energy"  :  -0.70143324
            "index"  :  32
           }
           ...
                
new feature: JmolSQL xxxx.select("<keys> WHEREIN <clause>")
  -- replaces ** idea
  -- assumes values of xxxx are themselves associative arrays
  -- indicates that the clause should be applied only to the values for the 
selected keys
  -- returns an associative array that holds only those keys for which the 
clause is true *for them*
  -- works also for array xxxx where each element of xxxx is an associative 
array, 
     in which case it returns a subarray of xxxx involving the matching values. 
 
  -- example:
  
   abc = [ A:[b:1], B:[b:2], C:[b:3, d:50], AA:[b:4] ]
   print abc.select("A* wherein b>2").format("JSON")
   
     { "AA": { "b": 4 } }
     
   Compare this to the following, where we are are checking for validity of abc 
itself:
   
   abc = [ A:[b:1], B:[b:2], C:[b:3, d:50], AA:[b:4] ]
   print abc.select("A* where B.b=2").format("JSON")
   
     { "A": { "b": 1 },"AA": { "b": 4 } }

   
   abc2 = [ 
      [ A:[b:1], B:[b:2], C:[b:3, d:50], AA:[b:4] ],
      [ A:[b:11], B:[b:22], C:[b:33, d:5050], AA:[b:40] ]
      ]
   print abc2.select("A* wherein b>2").format("JSON")
    
   [ { "AA": { "b": 4 } },{ "A": { "b": 11 },"AA": { "b": 40 } } ]      

new feature: JmolSQL xxxx.select("(<keys>) WHERE/WHEREIN <clause>")
  -- parentheses around keys replaces ";" idea
  -- indicates to return an array of values rather than a key/value pair
  -- works also for array xxxx where each element of xxxx is an array or 
associative array, 
     in which case it returns an array of matching values, without keys.  
  -- example:

   load =1ehz/dssr
   print _M.dssr.hbonds.select("distance where res_long like '*|C|72|*'")
          {
            "distance"  :  2.832
           }
          {
            "distance"  :  2.879
           }
          {
            "distance"  :  2.838
           }

        print _M.dssr.hbonds.select("(distance) where res_long like '*|C|72|*'")
        
                2.832
                2.879
                2.838

        print _M.dssr.hbonds.select("(distance) where res_long like 
'*|C|72|*'").average

                2.8496666
       

So...

JmolSQL

There are three parts to JmolSQL, object, keys, and an optional WHERE or WHERIN 
phrase:

object.SELECT("keys WHERE/WHEREIN phrase")

The object can be either an associative array [key1:value1, key2:value2, 
key3:value3] 
or an array of associative arrays, usually with the same set of keys.

Associative Arrays

When the top-level array is an associative array, select() can be used to 
select 
out subsets of the array, either as a single associative array or as an array 
of values.

abc.select("A,B")

returns a subset of abc. Wild cards can be interspersed with additional keys, 
for example, "a*,b" or "*_id". In each such case, the case-sensitive LIKE 
operation is used to match keys.

abc = [ A:[b:1],B:[b:2],AA:[b:3, d:50] ]
print  abc.select("A").format("JSON")

{ "A": { "b": 1 } }

abc = [ A:[b:1],B:[b:2],AA:[b:3, d:50] ]
print  abc.select("A*").format("JSON")

{ "A": { "b": 1 },"AA": { "b": 3,"d": 50 } }

abc.select("(...)")

Using parentheses around the list of keys delivers a list of values of b for 
only the the subset of xyz for which a=1:

abc = [ A:[b:1],B:[b:2],AA:[b:3, d:50] ]
print  abc.select("(A,B)").format("JSON")

[ { "b": 2 },{ "b": 1 } ]

Arrays of Associative Arrays

Generally we assume here that the elements of the array are associative arrays, 
all with the 
same set of keys. This is the essence of a database.  Whether or not the 
associative 
arrays have the same keys is not important for Jmol, but is typical for a 
database.

For example:

xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]


xyz.select("b")

Creates the sublist of associative arrays having the selected subset of keys:

xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
print xyz.select("b").format("JSON")

[ { "b": 11 },{ "b": 22 },{ "b": 33 } ]

xyz.select("(...)")

Creates a list of only the b values of each of x, y, and z, in order:

xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
print xyz.select("(b)").format("JSON")

[ 11,22,33 ]

xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
print xyz.select("(a,b)").format("JSON")

[ 11,1,22,2,33,3 ]

The assumption when using (keys) is that you want to know all these values, 
but you don't care what order they are in (because that will not be 
predictable) 
and you don't care about their exact context. An example is a list of bonds for 
which we just want to know all the atoms involved, but the atoms are listed 
under "atom1" and "atom2" in each bond array.

load =1ehz/dssr
select on @{_M.dssr.hbonds.select("(atom1_id,atom2_id)")}

206 atoms selected

Using WHERE

WHERE is used to select a subset of the elements of an array based on specific
key-value relationships.

xyz.select("... WHERE ...")

Delivers all key/value pairs in the subset of xyz element associative arrays 
for which the WHERE clause is true for that element. 

xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
print xyz.select("* where a<3 and b<20").format("JSON")

[ { "b": 11,"a": 1 } ]

xyz.select("(...) where ...")

Using parentheses around the list of keys delivers a list of 
values of for only the the subset of xyz for which the WHERE clause is true:

xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
print xyz.select("(b) where a>1").format("JSON")

[ 22,33 ]

Note that "... where..." will "drill down" through arrays of arrays to find the 
targeted associative array elements, producing a flat array of those objects:

xyz = [ [[a:1,b:11], [a:0,b:0]],[[[a:2,b:22]]],[[a:3,b:33,aa:44]] ]
print xyz.select("a* where a>0").format("JSON")

[ { "a": 1 },{ "a": 2 },{ "a": 3,"aa": 44 } ]

xyz = [ [[a:1,b:11], [a:0,b:0]],[[[a:2,b:22]]],[[a:3,b:33,aa:44]] ]
print xyz.select("(b) where a>0").format("JSON")

[ 11,22,33 ]
 
WHERE vs. WHEREIN

Starting with Jmol 14.4, JmolSQL includes an additional option, WHEREIN. This 
option 
allows selecting specific key/value pairs for which the value is itself an 
associative 
array, and *that array* has a specific set of key/value relationships. Thus, the
clause is checked one level deeper in the structure.

For example, given the associative array

abc = [key_1:[type:"a", i:1],key_2:[type:"b", i:2],key_3:[type:"a", i:3]]

we can select out only those keys for which type='a':

abc = [key_1:[type:"a", i:1],key_2:[type:"b", i:2],key_3:[type:"a", i:3]]
print abc.select("* WHEREIN type='a'").format("JSON");

{ "key_3": { "i": 3,"type": "a" },"key_1": { "i": 1,"type": "a" } }

All of the options that involve WHERE also apply to WHEREIN. For example,
multiple keys can be specified, and keys can be surrounded by parentheses 
to return just the values:

abc = [key_1:[type:"a", i:1],key_2:[type:"b", i:2],key_3:[type:"a", i:3]]
print abc.select("(key_1,key2) WHEREIN type='a'").format("JSON");

[ { "i": 1,"type": "a" } ]

In addition, WHEREIN can be applied to arrays as well as associative arrays. 
In this case, the WHEREIN phrase applies to the elements of that array, which
are assumed to be associative arrays. For example, we can get a list of just
the orbitals that are of a given symmetry:

load http://chemapps.stolaf.edu/jmol/jsmol/data/no2_nbo.log.gz 2 filter "alpha"
x= _M.moData.select("mos wherein occupancy>0 and symmetry like '(B2)*' ")
print x.select("(index)").format("JSON")

[ 1,5,8,10 ]

Modified Paths:
--------------
    trunk/Jmol/src/org/jmol/adapter/readers/quantum/BasisFunctionReader.java
    trunk/Jmol/src/org/jmol/adapter/readers/quantum/GaussianReader.java
    trunk/Jmol/src/org/jmol/dssx/DSSR1.java
    trunk/Jmol/src/org/jmol/script/SV.java
    trunk/Jmol/src/org/jmol/script/ScriptMathProcessor.java
    trunk/Jmol/src/org/jmol/scriptext/MathExt.java
    trunk/Jmol/src/org/jmol/viewer/Jmol.properties
    trunk/Jmol/src/org/jmol/viewer/PropertyManager.java

Modified: 
trunk/Jmol/src/org/jmol/adapter/readers/quantum/BasisFunctionReader.java
===================================================================
--- trunk/Jmol/src/org/jmol/adapter/readers/quantum/BasisFunctionReader.java    
2015-09-25 05:54:27 UTC (rev 20796)
+++ trunk/Jmol/src/org/jmol/adapter/readers/quantum/BasisFunctionReader.java    
2015-09-28 10:02:39 UTC (rev 20797)
@@ -54,8 +54,10 @@
   protected int[][] dfCoefMaps;
   
   private String[] filterTokens;
-  private boolean filterIsNot; 
+  private boolean filterIsNot;
 
+  private String spin; 
+
   /**
    * check line for filter options
    * 
@@ -65,29 +67,31 @@
     boolean isHeader = (line.indexOf('\n') == 0);
     if (!isHeader && !doReadMolecularOrbitals)
       return false;
-    if (filter == null)
-      return true;
     boolean isOK = true;
-    int nOK = 0;
     line += " " + alphaBeta;
     String ucline = line.toUpperCase();
-    if (filterTokens == null) {
-      filterIsNot = (filter.indexOf("!") >= 0);
-      filterTokens = PT.getTokens(filter.replace('!', ' ').replace(',', ' ')
-          .replace(';', ' '));
+    if (filter != null) {
+      int nOK = 0;
+      if (filterTokens == null) {
+        filterIsNot = (filter.indexOf("!") >= 0);
+        filterTokens = PT.getTokens(filter.replace('!', ' ').replace(',', ' ')
+            .replace(';', ' '));
+      }
+      for (int i = 0; i < filterTokens.length; i++)
+        if (ucline.indexOf(filterTokens[i]) >= 0) {
+          if (!filterIsNot) {
+            nOK = filterTokens.length;
+            break;
+          }
+        } else if (filterIsNot) {
+          nOK++;
+        }
+      isOK = (nOK == filterTokens.length);
+      if (!isHeader)
+        Logger.info("filter MOs: " + isOK + " for \"" + line + "\"");
     }
-    for (int i = 0; i < filterTokens.length; i++)
-      if (ucline.indexOf(filterTokens[i]) >= 0) {
-        if (!filterIsNot) {
-          nOK = filterTokens.length;
-          break;
-        }
-      } else if (filterIsNot) {
-        nOK++;
-      }
-    isOK = (nOK == filterTokens.length);
-    if (!isHeader)
-      Logger.info("filter MOs: " + isOK + " for \"" + line + "\"");
+    spin = (ucline.indexOf("ALPHA") >= 0 ? "alpha"
+        : ucline.indexOf("BETA") >= 0 ? "beta" : null);
     return isOK;
   }
 
@@ -95,6 +99,9 @@
     if (dfCoefMaps != null)
       mo.put("dfCoefMaps", dfCoefMaps);
     orbitals.addLast(mo);
+    mo.put("index", Integer.valueOf(orbitals.size()));
+    if (spin != null)
+      mo.put("spin", spin);
   }
   
   public class MOEnergySorter implements Comparator<Object>{

Modified: trunk/Jmol/src/org/jmol/adapter/readers/quantum/GaussianReader.java
===================================================================
--- trunk/Jmol/src/org/jmol/adapter/readers/quantum/GaussianReader.java 
2015-09-25 05:54:27 UTC (rev 20796)
+++ trunk/Jmol/src/org/jmol/adapter/readers/quantum/GaussianReader.java 
2015-09-28 10:02:39 UTC (rev 20797)
@@ -506,6 +506,12 @@
     boolean isNOtype = line.contains("Natural Orbital"); //gfprint pop(full,NO)
     while (rd() != null && line.toUpperCase().indexOf("DENS") < 0) {
       String[] tokens;
+      if (line.indexOf("eta Molecular Orbital Coefficients") >= 0) {
+        addMOData(nThisLine, data, mos);
+        nThisLine = 0;
+        if (!filterMO())
+          break;
+      }
       if (line.indexOf("                    ") == 0) {
         addMOData(nThisLine, data, mos);
         if (isNOtype) {
@@ -518,7 +524,7 @@
         }
         for (int i = 0; i < nThisLine; i++) {
           mos[i] = new Hashtable<String, Object>();
-          data[i] = new  Lst<String>();
+          data[i] = new Lst<String>();
           String sym;
           if (isNOtype) {
             mos[i]
@@ -570,7 +576,7 @@
       }
     }
     addMOData(nThisLine, data, mos);
-    setMOData(moModelSet  != asc.atomSetCount); 
+    setMOData(moModelSet != asc.atomSetCount);
     moModelSet = asc.atomSetCount;
   }
 

Modified: trunk/Jmol/src/org/jmol/dssx/DSSR1.java
===================================================================
--- trunk/Jmol/src/org/jmol/dssx/DSSR1.java     2015-09-25 05:54:27 UTC (rev 
20796)
+++ trunk/Jmol/src/org/jmol/dssx/DSSR1.java     2015-09-28 10:02:39 UTC (rev 
20797)
@@ -320,13 +320,13 @@
       int pt = DSSR_PATHS.indexOf(".." + key) + 2;
       int len = key.length();
       while (pt >= 2 && len > 0) {
+        if (DSSR_PATHS.substring(pt + len, pt + len + 2).equals(".."))
+          key = "[select (" + key + ")]";
         dbObj = vwr.extractProperty(dbObj, key, -1);
         pt += len + 1;
         int pt1 = DSSR_PATHS.indexOf(".", pt);
         key = DSSR_PATHS.substring(pt, pt1);
         len = key.length();
-        if (key.indexOf(";") >= 0)
-          key = "[select " + key + "]";
       }
       bs.or(vwr.ms.getAtoms(T.sequence, dbObj.toString()));
       bs.and(bsModel);

Modified: trunk/Jmol/src/org/jmol/script/SV.java
===================================================================
--- trunk/Jmol/src/org/jmol/script/SV.java      2015-09-25 05:54:27 UTC (rev 
20796)
+++ trunk/Jmol/src/org/jmol/script/SV.java      2015-09-28 10:02:39 UTC (rev 
20797)
@@ -502,12 +502,11 @@
   }
 
   // there are reasons to use Token here rather than ScriptVariable
-  // some of these functions, in particular iValue, fValue, and sValue
+  // for some of these functions, in particular iValue, fValue, and sValue
   
   public static boolean bValue(T x) {
     switch (x == null ? nada : x.tok) {
     case on:
-    case hash:
     case context:
       return true;
     case off:
@@ -526,6 +525,8 @@
     case matrix3f:
     case matrix4f:
       return Math.abs(fValue(x)) > 0.0001f;
+    case hash:
+      return !((SV) x).getMap().isEmpty();
     default:
       return false;
     }
@@ -1369,16 +1370,18 @@
 
   protected class Sort implements Comparator<SV> {
     private int arrayPt;
+    private String myKey;
     
-    protected Sort(int arrayPt) {
+    protected Sort(int arrayPt, String myKey) {
       this.arrayPt = arrayPt;
+      this.myKey = myKey;
     }
     
     @Override
     public int compare(SV x, SV y) {
       if (x.tok != y.tok) {
-        if (x.tok == decimal || x.tok == integer
-            || y.tok == decimal || y.tok == integer) {
+        if (x.tok == decimal || x.tok == integer || y.tok == decimal
+            || y.tok == integer) {
           float fx = fValue(x);
           float fy = fValue(y);
           return (fx < fy ? -1 : fx > fy ? 1 : 0);
@@ -1400,6 +1403,11 @@
         if (iPt < 0 || iPt >= sx.size())
           return 0;
         return compare(sx.get(iPt), sy.get(iPt));
+      case hash:
+        if (myKey != null) {
+          return compare(x.getMap().get(myKey), y.getMap().get(myKey));
+        }
+        //$FALL-THROUGH$
       default:
         float fx = fValue(x);
         float fy = fValue(y);
@@ -1426,7 +1434,7 @@
           x.set(n, v);
         }
       } else {
-        Collections.sort(getList(), new Sort(--arrayPt));
+        Collections.sort(getList(), new Sort(--arrayPt, null));
       }
     }
     return this;
@@ -1734,4 +1742,12 @@
     }
     return vm;
   }
+
+  public SV sortMapArray(String key) {
+    Lst<SV> lst = getList();
+    if (lst != null) {      
+      Collections.sort(getList(), new Sort(0, key));
+    }
+    return this;
+  }
 }

Modified: trunk/Jmol/src/org/jmol/script/ScriptMathProcessor.java
===================================================================
--- trunk/Jmol/src/org/jmol/script/ScriptMathProcessor.java     2015-09-25 
05:54:27 UTC (rev 20796)
+++ trunk/Jmol/src/org/jmol/script/ScriptMathProcessor.java     2015-09-28 
10:02:39 UTC (rev 20797)
@@ -179,7 +179,7 @@
       try {
         addOp(T.tokenComma);
       } catch (ScriptException e) {
-        System.out.println("Error adding comma");
+       // System.out.println("Error adding comma");
       }      
     }
     if (++xPt == xStack.length)

Modified: trunk/Jmol/src/org/jmol/scriptext/MathExt.java
===================================================================
--- trunk/Jmol/src/org/jmol/scriptext/MathExt.java      2015-09-25 05:54:27 UTC 
(rev 20796)
+++ trunk/Jmol/src/org/jmol/scriptext/MathExt.java      2015-09-28 10:02:39 UTC 
(rev 20797)
@@ -114,8 +114,10 @@
     case T.push:
     case T.pop:
       return evaluateList(mp, op.intValue, args);
+    case T.leftsquare:
+      if (args.length == 0)
+        mp.wasX = false;
     case T.array:
-    case T.leftsquare:
       return evaluateArray(mp, args);
     case T.axisangle:
     case T.quaternion:
@@ -2526,6 +2528,9 @@
     if (args.length > 1)
       return false;
     if (tok == T.sort) {
+      if (args.length == 1 && args[0].tok == T.string) {
+        return mp.addX(mp.getX().sortMapArray(args[0].asString()));
+      }
       int n = (args.length == 0 ? 0 : args[0].asInt());
       return mp.addX(mp.getX().sortOrReverse(n));
     }

Modified: trunk/Jmol/src/org/jmol/viewer/Jmol.properties
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/Jmol.properties      2015-09-25 05:54:27 UTC 
(rev 20796)
+++ trunk/Jmol/src/org/jmol/viewer/Jmol.properties      2015-09-28 10:02:39 UTC 
(rev 20797)
@@ -61,8 +61,258 @@
 
 TODO: consider if models with no atoms will cause issues in relation to 
model.firstAtomIndex
 
-Jmol.___JmolVersion="14.3.16_2015.09.25"
+Jmol.___JmolVersion="14.3.16_2015.09.28"
 
+new feature: array.sort("key")
+ -- sorts an array of associative arrays by the specified associative array 
key.
+ -- example:
+ 
+       load sf6.smol
+       x= _M.moData.mos.select("energy,index where energy<0")
+       x.sort("energy").reverse
+       print x
+
+         {
+           "energy"  :  -0.67001194
+           "index"  :  35
+          }
+         {
+           "energy"  :  -0.67001194
+           "index"  :  34
+          }
+         {
+           "energy"  :  -0.67001194
+           "index"  :  33
+          }
+         {
+           "energy"  :  -0.70143324
+           "index"  :  32
+          }
+          ...
+               
+new feature: JmolSQL xxxx.select("<keys> WHEREIN <clause>")
+  -- replaces ** idea
+  -- assumes values of xxxx are themselves associative arrays
+  -- indicates that the clause should be applied only to the values for the 
selected keys
+  -- returns an associative array that holds only those keys for which the 
clause is true *for them*
+  -- works also for array xxxx where each element of xxxx is an associative 
array, 
+     in which case it returns a subarray of xxxx involving the matching 
values.  
+  -- example:
+  
+   abc = [ A:[b:1], B:[b:2], C:[b:3, d:50], AA:[b:4] ]
+   print abc.select("A* wherein b>2").format("JSON")
+   
+     { "AA": { "b": 4 } }
+     
+   Compare this to the following, where we are are checking for validity of 
abc itself:
+   
+   abc = [ A:[b:1], B:[b:2], C:[b:3, d:50], AA:[b:4] ]
+   print abc.select("A* where B.b=2").format("JSON")
+   
+     { "A": { "b": 1 },"AA": { "b": 4 } }
+
+   
+   abc2 = [ 
+      [ A:[b:1], B:[b:2], C:[b:3, d:50], AA:[b:4] ],
+      [ A:[b:11], B:[b:22], C:[b:33, d:5050], AA:[b:40] ]
+      ]
+   print abc2.select("A* wherein b>2").format("JSON")
+    
+   [ { "AA": { "b": 4 } },{ "A": { "b": 11 },"AA": { "b": 40 } } ]      
+
+new feature: JmolSQL xxxx.select("(<keys>) WHERE/WHEREIN <clause>")
+  -- parentheses around keys replaces ";" idea
+  -- indicates to return an array of values rather than a key/value pair
+  -- works also for array xxxx where each element of xxxx is an array or 
associative array, 
+     in which case it returns an array of matching values, without keys.  
+  -- example:
+
+   load =1ehz/dssr
+   print _M.dssr.hbonds.select("distance where res_long like '*|C|72|*'")
+         {
+           "distance"  :  2.832
+          }
+         {
+           "distance"  :  2.879
+          }
+         {
+           "distance"  :  2.838
+          }
+
+       print _M.dssr.hbonds.select("(distance) where res_long like '*|C|72|*'")
+       
+               2.832
+               2.879
+               2.838
+
+       print _M.dssr.hbonds.select("(distance) where res_long like 
'*|C|72|*'").average
+
+               2.8496666
+       
+
+So...
+
+JmolSQL
+
+There are three parts to JmolSQL, object, keys, and an optional WHERE or 
WHERIN phrase:
+
+object.SELECT("keys WHERE/WHEREIN phrase")
+
+The object can be either an associative array [key1:value1, key2:value2, 
key3:value3] 
+or an array of associative arrays, usually with the same set of keys.
+
+Associative Arrays
+
+When the top-level array is an associative array, select() can be used to 
select 
+out subsets of the array, either as a single associative array or as an array 
of values.
+
+abc.select("A,B")
+
+returns a subset of abc. Wild cards can be interspersed with additional keys, 
+for example, "a*,b" or "*_id". In each such case, the case-sensitive LIKE 
operation is used to match keys.
+
+abc = [ A:[b:1],B:[b:2],AA:[b:3, d:50] ]
+print  abc.select("A").format("JSON")
+
+{ "A": { "b": 1 } }
+
+abc = [ A:[b:1],B:[b:2],AA:[b:3, d:50] ]
+print  abc.select("A*").format("JSON")
+
+{ "A": { "b": 1 },"AA": { "b": 3,"d": 50 } }
+
+abc.select("(...)")
+
+Using parentheses around the list of keys delivers a list of values of b for 
only the the subset of xyz for which a=1:
+
+abc = [ A:[b:1],B:[b:2],AA:[b:3, d:50] ]
+print  abc.select("(A,B)").format("JSON")
+
+[ { "b": 2 },{ "b": 1 } ]
+
+Arrays of Associative Arrays
+
+Generally we assume here that the elements of the array are associative 
arrays, all with the 
+same set of keys. This is the essence of a database.  Whether or not the 
associative 
+arrays have the same keys is not important for Jmol, but is typical for a 
database.
+
+For example:
+
+xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
+
+
+xyz.select("b")
+
+Creates the sublist of associative arrays having the selected subset of keys:
+
+xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
+print xyz.select("b").format("JSON")
+
+[ { "b": 11 },{ "b": 22 },{ "b": 33 } ]
+
+xyz.select("(...)")
+
+Creates a list of only the b values of each of x, y, and z, in order:
+
+xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
+print xyz.select("(b)").format("JSON")
+
+[ 11,22,33 ]
+
+xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
+print xyz.select("(a,b)").format("JSON")
+
+[ 11,1,22,2,33,3 ]
+
+The assumption when using (keys) is that you want to know all these values, 
+but you don't care what order they are in (because that will not be 
predictable) 
+and you don't care about their exact context. An example is a list of bonds 
for 
+which we just want to know all the atoms involved, but the atoms are listed 
+under "atom1" and "atom2" in each bond array.
+
+load =1ehz/dssr
+select on @{_M.dssr.hbonds.select("(atom1_id,atom2_id)")}
+
+206 atoms selected
+
+Using WHERE
+
+WHERE is used to select a subset of the elements of an array based on specific
+key-value relationships.
+
+xyz.select("... WHERE ...")
+
+Delivers all key/value pairs in the subset of xyz element associative arrays 
+for which the WHERE clause is true for that element. 
+
+xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
+print xyz.select("* where a<3 and b<20").format("JSON")
+
+[ { "b": 11,"a": 1 } ]
+
+xyz.select("(...) where ...")
+
+Using parentheses around the list of keys delivers a list of 
+values of for only the the subset of xyz for which the WHERE clause is true:
+
+xyz = [ [a:1,b:11],[a:2,b:22],[a:3,b:33] ]
+print xyz.select("(b) where a>1").format("JSON")
+
+[ 22,33 ]
+
+Note that "... where..." will "drill down" through arrays of arrays to find 
the 
+targeted associative array elements, producing a flat array of those objects:
+
+xyz = [ [[a:1,b:11], [a:0,b:0]],[[[a:2,b:22]]],[[a:3,b:33,aa:44]] ]
+print xyz.select("a* where a>0").format("JSON")
+
+[ { "a": 1 },{ "a": 2 },{ "a": 3,"aa": 44 } ]
+
+xyz = [ [[a:1,b:11], [a:0,b:0]],[[[a:2,b:22]]],[[a:3,b:33,aa:44]] ]
+print xyz.select("(b) where a>0").format("JSON")
+
+[ 11,22,33 ]
+ 
+WHERE vs. WHEREIN
+
+Starting with Jmol 14.4, JmolSQL includes an additional option, WHEREIN. This 
option 
+allows selecting specific key/value pairs for which the value is itself an 
associative 
+array, and *that array* has a specific set of key/value relationships. Thus, 
the
+clause is checked one level deeper in the structure.
+
+For example, given the associative array
+
+abc = [key_1:[type:"a", i:1],key_2:[type:"b", i:2],key_3:[type:"a", i:3]]
+
+we can select out only those keys for which type='a':
+
+abc = [key_1:[type:"a", i:1],key_2:[type:"b", i:2],key_3:[type:"a", i:3]]
+print abc.select("* WHEREIN type='a'").format("JSON");
+
+{ "key_3": { "i": 3,"type": "a" },"key_1": { "i": 1,"type": "a" } }
+
+All of the options that involve WHERE also apply to WHEREIN. For example,
+multiple keys can be specified, and keys can be surrounded by parentheses 
+to return just the values:
+
+abc = [key_1:[type:"a", i:1],key_2:[type:"b", i:2],key_3:[type:"a", i:3]]
+print abc.select("(key_1,key2) WHEREIN type='a'").format("JSON");
+
+[ { "i": 1,"type": "a" } ]
+
+In addition, WHEREIN can be applied to arrays as well as associative arrays. 
+In this case, the WHEREIN phrase applies to the elements of that array, which
+are assumed to be associative arrays. For example, we can get a list of just
+the orbitals that are of a given symmetry:
+
+load http://chemapps.stolaf.edu/jmol/jsmol/data/no2_nbo.log.gz 2 filter "alpha"
+x= _M.moData.select("mos wherein occupancy>0 and symmetry like '(B2)*' ")
+print x.select("(index)").format("JSON")
+
+[ 1,5,8,10 ]
+
+JmolVersion="14.3.16_2015.09.25"
+
 bug fix: HBONDS DELETE  broken since 14.1.2 
 
 JmolVersion="14.3.16_2015.09.23"
@@ -205,32 +455,6 @@
     "@stacks within(dssr,'stacks')",
     "@stems within(dssr,'stems')",
 
- new feature: jmolSQL [SELECT a;b ...]
-   -- semicolon rather than comma returns only the data, not the keys:
-   
-    load =1ehz/dssr
-    print _M.dssr.hbonds.select("nts_long;")
-   
-               |1|A|G|1||||,|1|A|C|2||||
-               |1|A|G|1||||,|1|A|C|72||||
-               |1|A|G|1||||,|1|A|C|72||||
-               |1|A|G|1||||,|1|A|C|72||||
-               ...
-
-    print _M.dssr.hbonds.select("nts_long,")
-
-         {
-           "nts_long"  :  "|1|A|C|60||||,|1|A|C|61||||"
-          }
-         {
-           "nts_long"  :  "|1|A|U|69||||,|1|A|C|70||||"
-          }
-         {
-           "nts_long"  :  "|1|A|C|75||||,|1|A|A|76||||"
-          }
-          
-        (Note: nts_long in _M.dssr.hbonds is being added by Jmol)
-   
 JmolVersion="14.3.16_2015.09.15"
 
 bug fix: JSME's noncanonical SMILES does not match Jmol's standard SMILES for 
aromatics
@@ -1749,7 +1973,7 @@
 
   print getProperty("auxiliaryInfo.models.aflowInfo['space group POST']") 
print getProperty("auxiliaryInfo.models.aflowInfo['Hf_atom [eV] (VASP)']") 
 
-Jmol's SQL selectors can be used to select out specific records.
+JmolSQL selectors can be used to select out specific records.
 
   print getProperty("auxiliaryInfo.models.aflowInfo[SELECT Cb,'Hf_atom [eV] 
(VASP)' WHERE Cb > 0.4 and Cb< 0.6]")
 
@@ -1826,11 +2050,12 @@
 bug fix: isolated bio groups such as AMP, which are not part of biopolymers, 
can result in crashes
          when colored or queried about their parameters (Hiroufmi Suzuki)
 
-bug fix: new SQL ** feature needed a tweak
+bug fix: new JmolSQL ** feature needed a tweak
 
 JmolVersion="14.3.13_2015.05.06"
 
-new feature: SQL "drilling" in associative arrays for sub arrays with a given 
property (Rolf Huehne)
+new feature: JmolSQL "drilling" in associative arrays for sub arrays with a 
given property (Rolf Huehne)
+   -- NOTE ADDED: replaced by WHEREIN in Jmol 14.3.16
    -- uses "**" as the SELECT option, sort of the way ** sometimes means 
"subdirectories of"
    -- returns an associative array or "" if no match
    -- example:
@@ -4526,7 +4751,6 @@
  -- defines unit cell in terms of an origin and three vectors
  -- just wasn't documented. 
   
-   
 
 JmolVersion="14.1.15_2014.05.06"
 
@@ -4729,9 +4953,7 @@
      select within(dssr,"bulges.3")
      select within(dssr,"bulges.3.linkedBy")
      select within(dssr,"basePairs[SELECT res* WHERE twist > 1]")
-   
-     
-          
+
 new feature: select x = [array or array variable]
  -- implied OR
  -- operators other than equals use implied AND

Modified: trunk/Jmol/src/org/jmol/viewer/PropertyManager.java
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/PropertyManager.java 2015-09-25 05:54:27 UTC 
(rev 20796)
+++ trunk/Jmol/src/org/jmol/viewer/PropertyManager.java 2015-09-28 10:02:39 UTC 
(rev 20797)
@@ -265,6 +265,9 @@
       propertyName = fixSelectQuotes(propertyName);
     while ((pt = lc.indexOf("[select ", ++pt)) >= 0) {
       int pt2 = lc.indexOf(" where ", pt);
+      int pt2b = lc.indexOf(" wherein ", pt);
+      if (pt2b > 0 && pt2b < pt2)
+        pt2 = pt2b;
       int pt3 = lc.lastIndexOf("]");
       if (pt2 < 0 || pt2 > pt3)
         continue;
@@ -432,52 +435,92 @@
         String key;
         boolean asMap = false;
         boolean asArray = false;
+        boolean isCaseSensitive = false;
+        Lst<String> keys = (arg.tok == T.select ? (Lst<String>) ((Object[]) 
arg.value)[0]
+            : null);
+        T[] whereArgs = null;
         if (arg.tok == T.select) {
+          isCaseSensitive = true;
+          whereArgs = (T[]) ((Object[]) arg.value)[1];
           key = arg.myName;
+          asArray = (key.indexOf(";") >= 0);
           if (key.contains("**")) {
-            asArray = key.endsWith(";");
-            if (asArray)
-              key = key.substring(0, key.length() - 1);
-            boolean isAll = key.equals("**");
-            key = PT.rep(key, "**", "*");
+            boolean isAll = keys.size() == 0;
             String newKey = "";
             for (Entry<?, ?> e : h.entrySet()) {
               String k = (String) e.getKey();
-              if (!isAll && !PT.isLike(k, key))
-                continue;
-              Object o = e.getValue();
-              if ((o instanceof SV)) {
-                o = ((SV) o).getMap();
-              } else if (!(o instanceof Map<?, ?>)) {
-                o = null;
+              for (int i = (isAll ? 1 : keys.size()); --i >= 0;) {
+                if (!isAll && !PT.isLike(k, keys.get(i)))
+                  continue;
+                Object o = e.getValue();
+                boolean isList = false;
+                switch (o instanceof SV ? ((SV) o).tok : T.nada) {
+                case T.varray:
+                  isList = true;
+                  o = ((SV) o).getList();
+                  break;
+                case T.hash:
+                case T.context:
+                  o = ((SV) o).getMap();
+                  break;
+                default:
+                  if (!(o instanceof Map<?, ?>)
+                      && !(isList = (o instanceof Lst<?>)))
+                    o = null;
+                }
+                if (o != null) {
+                  if (isList) {
+                    if (v2 == null)
+                      v2 = new Lst<Object>();
+                    Lst<?> olst = (Lst<?>) o;
+                    for (int n = olst.size(), j = 0; j < n; j++) {
+                      o = olst.get(j);
+                      if (!(o instanceof SV) || (o = ((SV) o).getMap()) == 
null)
+                        continue;
+                      if (whereArgs == null
+                          || vwr.checkSelect((Map<String, SV>) o, whereArgs))
+                        v2.addLast(o);
+                    }
+                    return v2;
+                  }
+                  if (whereArgs == null
+                      || vwr.checkSelect((Map<String, SV>) o, whereArgs))
+                    newKey += "," + k;
+                }
               }
-              if (o != null
-                  && vwr.checkSelect((Map<String, SV>) o, (T[]) arg.value))
-                newKey += "," + k;
             }
             if (newKey.length() == 0)
-              return "";
+              return new Lst<SV>();
             key = newKey.substring(1);
             asMap = !asArray;
-          } else if (!vwr.checkSelect((Map<String, SV>) property,
-              (T[]) arg.value))
+            keys = null;
+          } else if (whereArgs != null
+              && !vwr.checkSelect((Map<String, SV>) property, whereArgs))
             return "";
         } else {
           key = arg.asString();
           if (key.equalsIgnoreCase("keys")) {
-            Lst<Object> keys = new Lst<Object>();
+            Lst<Object> lst = new Lst<Object>();
             for (String k : h.keySet())
-              keys.addLast(k);
-            return extractProperty(keys, args, ptr, null, true);
+              lst.addLast(k);
+            return extractProperty(lst, args, ptr, null, true);
           }
         }
-        boolean isWild = (asArray || key.startsWith("*") || key.endsWith("*") 
-            || key.indexOf(",") >= 0 || key.indexOf(";") >= 0);
+        boolean havePunctuation = (asArray || key.indexOf(",") >= 0 || key
+            .indexOf(";") >= 0);
+        if (isCaseSensitive && !havePunctuation) {
+          havePunctuation = true;
+          key += ",";
+        }
+        boolean isWild = (asArray || key.startsWith("*") || key.endsWith("*") 
|| havePunctuation);
+        boolean wasV2 = (v2 != null);
         if (isWild) {
-          if (v2 == null)
+          if (!wasV2)
             v2 = new Lst<Object>();
-          if (!asArray && key.length() == 1) {
+          if (!asArray && (keys == null ? key.length() == 1 : keys.size() == 
0)) {
             if (ptr == ((SV[]) args).length) {
+              if (!wasV2)
+                return property;
               v2.addLast(property);
               return v2;
             }
@@ -485,23 +528,38 @@
           }
         }
         if (key.contains("**"))
-          key = PT.rep(key, "**", "*") + ",";
+          key = PT.rep(key, "**", "*");
+        if (isWild && !havePunctuation)
+          key += ",";
         if (asMap || asArray || key.contains(",")) {
           Map<String, Object> mapNew = new Hashtable<String, Object>();
-          String[] tokens = PT.split(key, ",");
-          for (int i = tokens.length; --i >= 0;)
-            getMapSubset(h, tokens[i], mapNew);
-          if (asMap)
+          if (keys != null && keys.size() == 0) {
+            keys = null;
+            key = "*";
+          }
+          if (keys == null) {
+            String[] tokens = PT.split(key, ",");
+            for (int i = tokens.length; --i >= 0;)
+              getMapSubset(h, tokens[i], mapNew, asArray ? v2 : null);
+          } else {
+            for (int i = keys.size(); --i >= 0;)
+              getMapSubset(h, keys.get(i), mapNew, asArray ? v2 : null);
+          }
+          if (asMap && !wasV2)
             return mapNew;
           if (ptr == ((SV[]) args).length) {
-            v2.addLast(mapNew);
+            if (!asArray) {
+              if (!wasV2)
+                return mapNew;
+              v2.addLast(mapNew);
+            }
             return v2;
           }
-          return extractProperty(mapNew, args, ptr, v2, true);
+          return extractProperty(mapNew, args, ptr, (wasV2 ? v2 : null), true);
         }
-        key = checkMap(h, key, isWild, v2, args, ptr);
+        key = checkMap(h, key, isWild, v2, args, ptr, isCaseSensitive);
         return (key != null && !isWild ? extractProperty(h.get(key), args, ptr,
-            null, true) : isWild ? v2 : "");
+            null, true) : !isWild ? "" : wasV2 ? v2 : v2);
       }
       if (property instanceof Lst<?>) {
         // drill down into vectors for this key
@@ -524,18 +582,25 @@
   }
 
   private static void getMapSubset(Map<String, ?> h, String key,
-                                   Map<String, Object> h2) {
+                                   Map<String, Object> h2, Lst<Object> v2) {
     if (key.startsWith("\"") || key.startsWith("'"))
       key = PT.trim(key,  "\"'");
     Object val = h.get(key);
     if (val != null) {
-      h2.put(key, val);
+      if (v2 == null)
+        h2.put(key, val);
+      else
+        v2.addLast(val);
       return;
     }
     for (Entry<String, ?> e : h.entrySet()) {
       String k = e.getKey();
-      if (PT.isLike(k, key))
-        h2.put(k, e.getValue());
+      if (PT.isLike(k, key)) {
+        if (v2 == null)
+          h2.put(k, e.getValue());
+        else
+          v2.addLast(e.getValue());
+      }
     }
   }
 
@@ -546,20 +611,32 @@
         String key = (String) args[i].value;
         // SELECT nt* WHERE name!=WC
         // SELECT a,b,c WHERE x.in(...)
-        
-        if (key.toUpperCase().startsWith("SELECT ")) {
+
+        String ucKey = key.toUpperCase();
+        if (ucKey.startsWith("SELECT ")) {
           if (argsNew == null)
             argsNew = (SV[]) AU.arrayCopyObject(args, args.length);
-          key = key.substring(6).trim();
-          if (key.toUpperCase().startsWith("WHERE "))
-            key = "* " + key;
-          int pt = key.toUpperCase().indexOf(" WHERE ");
+          ucKey = (key = key.substring(6).trim()).toUpperCase();
+          if (ucKey.startsWith("WHERE ") || ucKey.startsWith("WHEREIN "))
+            ucKey = (key = "* " + key).toUpperCase();
+          int pt = ucKey.indexOf(" WHEREIN ");
+          String ext = (pt < 0 ? "" : key.indexOf(";") >= 0 ? ";**" : ",**");
+          if (pt < 0)
+            pt = ucKey.indexOf(" WHERE ");
+          String select = key.substring(0, pt < 0 ? key.length() : pt).trim();
+          if (select.startsWith("(") && select.endsWith(")"))
+            select = select.substring(1, select.length() - 1) + ";";          
           if (pt < 0) {
-            argsNew[i].value = key;
+            argsNew[i] = SV.newV(T.select, new Object[] { getKeys(select), 
null });
+            argsNew[i].myName = select;
           } else {
-            argsNew[i] = SV.newV(T.select, vwr.compileExpr(key.substring(pt + 
6)
-                .trim()));
-            argsNew[i].myName = key.substring(0, pt).trim();
+            // allow for (A*,B) to be same as A*;B
+            select += ext;
+            argsNew[i] = SV.newV(
+                T.select,
+                new Object[] { getKeys(select),
+                    vwr.compileExpr(key.substring(pt + 6 + 
ext.length()).trim()) });
+            argsNew[i].myName = select;
           }
         }
       }
@@ -567,29 +644,67 @@
     return (argsNew == null ? args : argsNew);
   }
 
+  private Lst<String> getKeys(String select) {
+    Lst<String> keys = new Lst<String>();
+    select = PT.rep(PT.rep(select, "**", "*"), ";",",") + ",";
+    int pt0 = 0, pt1 = -1;
+    while ((pt1 = select.indexOf(",", pt1 + 1)) >= 0) {
+      if (pt1 > pt0) {
+        String key = select.substring(pt0, pt1);
+        if (key.equals("*")) {
+          // skip "*" if at end; it may  just indicated **
+          if (keys.size() == 0)
+            return keys;
+          continue;
+        }
+        keys.addLast(key);
+        pt0 = pt1 + 1;
+      }
+    }
+    return keys;
+  }
+
   private String checkMap(Map<String, ?> h, String key, boolean isWild,
-                          Lst<Object> v2, Object args, int ptr) {
+                          Lst<Object> v2, Object args, int ptr, boolean 
isCaseSensitive) {
     boolean isOK = (v2 == null && h.containsKey(key));
     if (!isOK) {
       boolean hasSemi = key.contains(";");
-      if (hasSemi)
-        key = ";" + key + ";";
-      String lckey = (isWild ? key.toLowerCase() : null);
+      String[] keys = (hasSemi ? PT.split(key, ";") : null);
+      String lckey = (isWild && !isCaseSensitive ? key.toLowerCase() : null);
       for (String k : h.keySet()) {
-        if (hasSemi ? key.contains(";" + k + ";") 
-            : k.equalsIgnoreCase(key) || lckey != null
-            && PT.isLike(k.toLowerCase(), lckey)) {
-          if (v2 == null)
-            return k;
-          v2.addLast(extractProperty(h.get(k), args, ptr, null, true));
-          if (!isWild && !hasSemi)
-            return null;
-        }
+        if (hasSemi) {
+          for (int i = keys.length; --i >= 0; key = null) {
+            key = keys[i];
+            if (key.length() == 0)
+              continue;
+            if (isCaseSensitive) {
+              if (!PT.isLike(k, key))
+                continue;
+              break;
+            }
+            lckey = (key.indexOf("*") >= 0 ? key.toLowerCase() : null);
+            if (checkKey(k, key, lckey))
+              break;
+          }
+          if (key == null)
+            continue;
+        } else if (isCaseSensitive ? !PT.isLike(k,  key) : !checkKey(k, key, 
lckey))
+          continue;
+        if (v2 == null)
+          return k;
+        v2.addLast(extractProperty(h.get(k), args, ptr, null, true));
+        if (!isWild && !hasSemi)
+          return null;
       }
     }
     return (isOK ? key : null);
   }
 
+  private boolean checkKey(String k, String key, String lckey) {
+    return k.equalsIgnoreCase(key) ||
+        lckey != null && PT.isLike(k.toLowerCase(), lckey);
+  }
+
   private static String getPropertyName(int propID) {
     return (propID < 0 ? "" : propertyTypes[propID * 3]);
   }
@@ -721,7 +836,7 @@
       String paramType = getParamType(i);
       String paramDefault = getDefaultPropertyParam(i);
       String name = getPropertyName(i);
-      data[i] = (name.charAt(0) == 'X' ? "" : name
+      data[i] = (name.length() ==  0 || name.charAt(0) == 'X' ? "" : name
           + (paramType != "" ? " "
               + getParamType(i)
               + (paramDefault != "" ? " #default: "

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.


------------------------------------------------------------------------------
_______________________________________________
Jmol-commits mailing list
Jmol-commits@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jmol-commits

Reply via email to