cql2pqf: refactor truncation handling and fix Z39.58 mode
authorAdam Dickmeiss <adam@indexdata.dk>
Tue, 30 Aug 2011 10:26:29 +0000 (12:26 +0200)
committerAdam Dickmeiss <adam@indexdata.dk>
Tue, 30 Aug 2011 10:26:29 +0000 (12:26 +0200)
Refactor the CQL to PQF term code in cqltransform.c. Characters that
don't need escaping aren't and characters that do need escaping are.
For example CQL query a#a? must be handled as a Z39.58 mode query
because the trailing ? (single-character mask in CQL). But since the
generated term includes #, it must be escaped and survive the PQF parse,
thus the result is "\\#a#" (was "a#a# before). Also the CQL term a*3 now
produces "a?\\3" rather than "a?3" . This is becauase a?n has special
meaning in Z39.58. Characters that do need escaping are the PQF specials
" and \\. Rest are preseved and \-sequence in CQL are otherwise removed.

50 files changed:
src/cqltransform.c
test/cql2pqf/1.1.out
test/cql2pqf/1.2.out
test/cql2pqf/1.3.out
test/cql2pqf/2.1.out
test/cql2pqf/2.2.out
test/cql2pqf/3.1.out
test/cql2pqf/3.2.out
test/cql2pqf/3.3.out
test/cql2pqf/3.4.out
test/cql2pqf/3.5.out
test/cql2pqf/3.6.out
test/cql2pqf/3.7.out
test/cql2pqf/3.8.out
test/cql2pqf/3.9.out
test/cql2pqf/4.1.err [new file with mode: 0644]
test/cql2pqf/4.1.out [new file with mode: 0644]
test/cql2pqf/4.10.err [new file with mode: 0644]
test/cql2pqf/4.10.out [new file with mode: 0644]
test/cql2pqf/4.11.err [new file with mode: 0644]
test/cql2pqf/4.11.out [new file with mode: 0644]
test/cql2pqf/4.12.err [new file with mode: 0644]
test/cql2pqf/4.12.out [new file with mode: 0644]
test/cql2pqf/4.2.err [new file with mode: 0644]
test/cql2pqf/4.2.out [new file with mode: 0644]
test/cql2pqf/4.3.err [new file with mode: 0644]
test/cql2pqf/4.3.out [new file with mode: 0644]
test/cql2pqf/4.4.err [new file with mode: 0644]
test/cql2pqf/4.4.out [new file with mode: 0644]
test/cql2pqf/4.5.err [new file with mode: 0644]
test/cql2pqf/4.5.out [new file with mode: 0644]
test/cql2pqf/4.6.err [new file with mode: 0644]
test/cql2pqf/4.6.out [new file with mode: 0644]
test/cql2pqf/4.7.err [new file with mode: 0644]
test/cql2pqf/4.7.out [new file with mode: 0644]
test/cql2pqf/4.8.err [new file with mode: 0644]
test/cql2pqf/4.8.out [new file with mode: 0644]
test/cql2pqf/4.9.err [new file with mode: 0644]
test/cql2pqf/4.9.out [new file with mode: 0644]
test/cql2pqf/5.1.err [new file with mode: 0644]
test/cql2pqf/5.1.out [new file with mode: 0644]
test/cql2pqf/5.2.err [new file with mode: 0644]
test/cql2pqf/5.2.out [new file with mode: 0644]
test/cql2pqf/5.3.err [new file with mode: 0644]
test/cql2pqf/5.3.out [new file with mode: 0644]
test/cql2pqf/5.4.err [new file with mode: 0644]
test/cql2pqf/5.4.out [new file with mode: 0644]
test/cql2pqfsample
test/test_cql2pqf.sh
util/cql2pqf.c

index 3163775..117ca97 100644 (file)
@@ -617,7 +617,7 @@ static void emit_term(cql_transform_t ct,
     int i;
     const char *ns = cn->u.st.index_uri;
     int process_term = !has_modifier(cn, "regexp");
-    char *z3958_mem = 0;
+    int z3958_mode = 0;
 
     assert(cn->which == CQL_NODE_ST);
 
@@ -685,34 +685,12 @@ static void emit_term(cql_transform_t ct,
         }
         else if (first_wc)
         {
-            /* We have one or more wildcard characters, but not in a
-             * way that can be dealt with using only the standard
-             * left-, right- and both-truncation attributes.  We need
-             * to translate the pattern into a Z39.58-type pattern,
-             * which has been supported in BIB-1 since 1996.  If
-             * there's no configuration element for "truncation.z3958"
-             * we indicate this as error 28 "Masking character not
-             * supported".
-             */
-            int i;
+            z3958_mode = 1;
             cql_pr_attr(ct, "truncation", "z3958", 0,
                         pr, client_data, YAZ_SRW_MASKING_CHAR_UNSUPP);
-            z3958_mem = (char *) xmalloc(length+1);
-            for (i = 0; i < length; i++)
-            {
-                if (i > 0 && term[i-1] == '\\')
-                    z3958_mem[i] = term[i];
-                else if (term[i] == '*')
-                    z3958_mem[i] = '?';
-                else if (term[i] == '?')
-                    z3958_mem[i] = '#';
-                else
-                    z3958_mem[i] = term[i];
-            }
-            z3958_mem[length] = '\0';
-            term = z3958_mem;
         }
-        else {
+        else
+        {
             /* No masking characters.  Use "truncation.none" if given. */
             cql_pr_attr(ct, "truncation", "none", 0,
                         pr, client_data, 0);
@@ -733,20 +711,47 @@ static void emit_term(cql_transform_t ct,
         }
     }
 
+    /* produce only \-sequences if:
+       1) the output is a Z39.58-trunc reserved character
+       2) the output is a PQF reserved character (\\, \")
+    */
     (*pr)("\"", client_data);
     for (i = 0; i < length; i++)
     {
-        /* pr(int) each character */
-        /* we do not need to deal with \-sequences because the
-           CQL and PQF terms have same \-format, bug #1988 */
-        char buf[2];
-
-        buf[0] = term[i];
-        buf[1] = '\0';
-        (*pr)(buf, client_data);
+        char x[3]; /* temp buffer */
+        if (i > 0 && term[i-1] == '\\')
+        {
+            if (term[i] == '\"' || term[i] == '\\')
+                pr("\\", client_data);
+            if (z3958_mode && strchr("#?", term[i]))
+                pr("\\\\", client_data); /* double \\ to survive PQF parse */
+            x[0] = term[i];
+            x[1] = '\0';
+            pr(x, client_data);
+        }
+        else if (z3958_mode && term[i] == '*')
+        {
+            pr("?", client_data);
+            /* avoid ?n sequences output (n=[0-9]) because that has
+               different semantics than just a single ? in Z39.58
+            */
+            if (i < length - 1 && yaz_isdigit(term[i+1]))
+                pr("\\\\", client_data); /* double \\ to survive PQF parse */
+        }
+        else if (z3958_mode && term[i] == '?')
+            pr("#", client_data);
+        else if (term[i] != '\\')
+        {
+            if (term[i] == '\"')
+                pr("\\", client_data);
+            if (z3958_mode && strchr("#?", term[i]))
+                pr("\\\\", client_data); /* double \\ to survive PQF parse */
+            x[0] = term[i];
+            x[1] = '\0';
+            pr(x, client_data);
+        }
     }
     (*pr)("\" ", client_data);
-    xfree(z3958_mem);
 }
 
 static void emit_terms(cql_transform_t ct,
index b881596..7791c20 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL a
 @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "a" 
index de3e6a9..413ec53 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL a b
 @attr 6=1 @attr 2=3 @attr 4=1 @and @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "a" @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "b" 
index e5f2c38..7ead55a 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL "a b"
 @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "a b" 
index 22f9db6..43777c9 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL title = fish
 @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=4 "fish" 
index 22f9db6..dda42e5 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL dc.title = fish
 @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=4 "fish" 
index bc0d04d..e60d2ab 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL cat or dog
 @or @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "cat" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "dog" 
index 7e88a2c..f97d972 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL cat and fish
 @and @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "cat" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "fish" 
index 6a9dd4a..ebc4c26 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL cat not frog
 @not @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "cat" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "frog" 
index 6a9dd4a..7204858 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL (cat not frog)
 @not @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "cat" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "frog" 
index 893ab74..437b006 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL "cat" not "fish food"
 @not @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "cat" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "fish food" 
index 7c9d99c..bfb40b6 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL xml and "prox///"
 @and @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "xml" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "prox///" 
index 65f6e63..6ab6f24 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL fred and any
 @and @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "fred" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "any" 
index d43365b..9a4c255 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL ((fred or all))
 @or @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "fred" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "all" 
index e1e1637..974d023 100644 (file)
@@ -1 +1,2 @@
+Parsing CQL a or b and c not d 
 @not @and @or @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "a" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "b" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "c" @attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "d" 
diff --git a/test/cql2pqf/4.1.err b/test/cql2pqf/4.1.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.1.out b/test/cql2pqf/4.1.out
new file mode 100644 (file)
index 0000000..0de660d
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL *a
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=2 @attr 1=1016 "a" 
diff --git a/test/cql2pqf/4.10.err b/test/cql2pqf/4.10.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.10.out b/test/cql2pqf/4.10.out
new file mode 100644 (file)
index 0000000..ad7816f
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL *a#
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=2 @attr 1=1016 "a#" 
diff --git a/test/cql2pqf/4.11.err b/test/cql2pqf/4.11.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.11.out b/test/cql2pqf/4.11.out
new file mode 100644 (file)
index 0000000..3d875dd
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL a#a*
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=1 @attr 1=1016 "a#a" 
diff --git a/test/cql2pqf/4.12.err b/test/cql2pqf/4.12.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.12.out b/test/cql2pqf/4.12.out
new file mode 100644 (file)
index 0000000..777b067
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL a*3
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "a?\\3" 
diff --git a/test/cql2pqf/4.2.err b/test/cql2pqf/4.2.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.2.out b/test/cql2pqf/4.2.out
new file mode 100644 (file)
index 0000000..0da3876
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL a*
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=1 @attr 1=1016 "a" 
diff --git a/test/cql2pqf/4.3.err b/test/cql2pqf/4.3.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.3.out b/test/cql2pqf/4.3.out
new file mode 100644 (file)
index 0000000..525bec3
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL *a*
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=3 @attr 1=1016 "a" 
diff --git a/test/cql2pqf/4.4.err b/test/cql2pqf/4.4.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.4.out b/test/cql2pqf/4.4.out
new file mode 100644 (file)
index 0000000..574804a
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL a*a
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "a?a" 
diff --git a/test/cql2pqf/4.5.err b/test/cql2pqf/4.5.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.5.out b/test/cql2pqf/4.5.out
new file mode 100644 (file)
index 0000000..ee840c1
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL ?a
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "#a" 
diff --git a/test/cql2pqf/4.6.err b/test/cql2pqf/4.6.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.6.out b/test/cql2pqf/4.6.out
new file mode 100644 (file)
index 0000000..d6d0796
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL a?
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "a#" 
diff --git a/test/cql2pqf/4.7.err b/test/cql2pqf/4.7.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.7.out b/test/cql2pqf/4.7.out
new file mode 100644 (file)
index 0000000..77527e5
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL ?a?
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "#a#" 
diff --git a/test/cql2pqf/4.8.err b/test/cql2pqf/4.8.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.8.out b/test/cql2pqf/4.8.out
new file mode 100644 (file)
index 0000000..3934395
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL a?a
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "a#a" 
diff --git a/test/cql2pqf/4.9.err b/test/cql2pqf/4.9.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/4.9.out b/test/cql2pqf/4.9.out
new file mode 100644 (file)
index 0000000..300cf29
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL a#a?
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "a\\#a#" 
diff --git a/test/cql2pqf/5.1.err b/test/cql2pqf/5.1.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/5.1.out b/test/cql2pqf/5.1.out
new file mode 100644 (file)
index 0000000..45e62bf
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL a*\3
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "a?3" 
diff --git a/test/cql2pqf/5.2.err b/test/cql2pqf/5.2.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/5.2.out b/test/cql2pqf/5.2.out
new file mode 100644 (file)
index 0000000..8f7671e
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL \*a#
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "*a#" 
diff --git a/test/cql2pqf/5.3.err b/test/cql2pqf/5.3.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/5.3.out b/test/cql2pqf/5.3.out
new file mode 100644 (file)
index 0000000..9c70637
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL \*a*b#
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=104 @attr 1=1016 "*a?b\\#" 
diff --git a/test/cql2pqf/5.4.err b/test/cql2pqf/5.4.err
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cql2pqf/5.4.out b/test/cql2pqf/5.4.out
new file mode 100644 (file)
index 0000000..0005a1c
--- /dev/null
@@ -0,0 +1,2 @@
+Parsing CQL \"
+@attr 6=1 @attr 2=3 @attr 4=1 @attr 3=3 @attr 6=1 @attr 5=100 @attr 1=1016 "\"" 
index 166bcca..a11bbd4 100644 (file)
@@ -1,18 +1,15 @@
 # CQL queries for testing.
-# from http://www.loc.gov/z3950/agency/zing/cql/sample-queries.html
+# Read by test_cql2pqf.sh
+# No blank lines!
+#
 # Simple Term
-
 a
 a b
 "a b"
-
 # Index Relation Term
-
 title = fish
 dc.title = fish
-
 # Simple Boolean
-
 cat or dog
 cat and fish
 cat not frog
@@ -22,4 +19,21 @@ xml and "prox///"
 fred and any
 ((fred or all))
 a or b and c not d 
-
+# Masking/Truncation
+*a
+a*
+*a*
+a*a
+?a
+a?
+?a?
+a?a
+a#a?
+*a#
+a#a*
+a*3
+# Escape sequences (sh reads one slash, though)
+a*\\3
+\\*a#
+\\*a*b#
+\\"
index 052b8e4..429e38a 100755 (executable)
@@ -9,7 +9,7 @@ comment=0
 ecode=0
 test -f ${srcdir}/cql2pqfsample || exit 1
 test -d cql2pqf || mkdir cql2pqf
-for f in `cat ${srcdir}/cql2pqfsample`; do
+while read f; do
        if echo $f | grep '^#' >/dev/null; then
                comment=1
        else
@@ -24,7 +24,7 @@ for f in `cat ${srcdir}/cql2pqfsample`; do
                OUT2=cql2pqf/$secno.$testno.out.tmp
                ERR2=cql2pqf/$secno.$testno.err.tmp
                DIFF=cql2pqf/$secno.$testno.diff
-               ../util/cql2pqf ${srcdir}/../etc/pqf.properties "$f" >$OUT2 2>$ERR2
+               ../util/cql2pqf -v ${srcdir}/../etc/pqf.properties "$f" >$OUT2 2>$ERR2
                if test -f $OUT1 -a -f $ERR1; then
                        if diff $OUT1 $OUT2 >$DIFF; then
                                rm $DIFF
@@ -49,6 +49,6 @@ for f in `cat ${srcdir}/cql2pqfsample`; do
                        ecode=1
                fi      
        fi              
-done
+done < cql2pqfsample
 IFS="$oIFS"
 exit $ecode
index 40aa034..b449a4b 100644 (file)
@@ -26,11 +26,12 @@ int main(int argc, char **argv)
     char *query = 0;
     char *fname = 0;
     int reverse = 0;
+    int verbose = 1;
 
     int ret;
     char *arg;
 
-    while ((ret = options("n:r", argv, argc, &arg)) != -2)
+    while ((ret = options("n:rv", argv, argc, &arg)) != -2)
     {
         switch (ret)
         {
@@ -40,11 +41,14 @@ int main(int argc, char **argv)
             else
                 query = arg;
             break;
+        case 'n':
+            iterations = atoi(arg);
+            break;
         case 'r':
             reverse = 1;
             break;
-        case 'n':
-            iterations = atoi(arg);
+        case 'v':
+            verbose = 1;
             break;
         default:
             usage();
@@ -100,6 +104,8 @@ int main(int argc, char **argv)
         
         if (query)
         {
+            if (verbose)
+                printf("Parsing CQL %s\n", query);
             for (i = 0; i<iterations; i++)
                 r = cql_parser_string(cp, query);
         }