1313import java .io .IOException ;
1414import java .util .ArrayList ;
1515import java .util .HashMap ;
16+ import java .util .Iterator ;
1617import java .util .Map ;
1718import java .util .regex .Pattern ;
1819
2223 * Converts MathML queries into XQueries.
2324 * The result is then wrapped around with a header and a footer (defaults to a DB2 header/footer if not given).
2425 * The variable $x always represents a hit, so you can refer to $x in the footer as the result node.
26+ * The variable $q always represents a map of qvars to their respective formula ID, so you can refer to $q in the footer
27+ * to return qvar results.
2528 * Created by Moritz Schubotz on 9/3/14.
2629 * Translated from http://git.wikimedia.org/blob/mediawiki%2Fextensions%2FMathSearch.git/31a80ae48d1aaa50da9103cea2e45a8dc2204b39/XQueryGenerator.php
2730 */
@@ -31,7 +34,10 @@ public class XQueryGenerator {
3134 //Qvar map of qvar name to XPaths referenced by each qvar
3235 private Map <String , ArrayList <String >> qvar = new HashMap <>();
3336 private String relativeXPath = "" ;
37+ private String exactMatchXQuery = "" ;
3438 private String lengthConstraint = "" ;
39+ private String qvarConstraint = "" ;
40+ private String qvarMapVariable = "" ;
3541 private String header = "declare default element namespace \" http://www.w3.org/1998/Math/MathML\" ;\n " +
3642 "for $m in db2-fn:xmlcolumn(\" math.math_mathml\" ) return\n " ;
3743 private String footer = "data($m/*[1]/@alttext)" ;
@@ -134,80 +140,101 @@ public void setMainElement( Node mainElement ) {
134140
135141 /**
136142 * Generates the constraints of the XQuery and then builds the XQuery and returns it as a string
137- * @return XQuery as string
143+ * @return XQuery as string. Returns null if no main element set.
138144 */
139145 public String toString () {
140146 if ( mainElement == null ) {
141147 return null ;
142148 }
143- final String fixedConstraints = generateConstraints ( mainElement , true );
144- final StringBuilder qvarConstraintString = new StringBuilder ();
145- //Generate qvar constraint string
146- //This specifies that the same qvars must refer to the same nodes, using the XQuery "=" equality
147- //This is equality based on: same text, same node names, and same children by the "=" equality
148- for ( Map .Entry <String , ArrayList <String >> entry : qvar .entrySet () ) {
149- final StringBuilder addString = new StringBuilder ();
150- if ( entry .getValue ().size () > 1 ) {
151- final String firstEntry = entry .getValue ().get ( 0 );
152- if ( qvarConstraintString .length () != 0 ) {
153- addString .append ("\n and " );
154- }
155- String lastEntry = "" ;
156- boolean newContent = false ;
157- //begins at second entry
158- for ( final String currentEntry : entry .getValue () ) {
159- if ( !currentEntry .equals ( firstEntry ) ) {
160- if ( !lastEntry .isEmpty () ) {
161- addString .append (" and " );
162- }
163- addString .append ("$x" ).append (firstEntry ).append (" = $x" ).append (currentEntry );
164- lastEntry = currentEntry ;
165- newContent = true ;
166- }
167- }
168- if ( newContent ) {
169- qvarConstraintString .append (addString );
170- }
171- }
172- }
173- return getString ( mainElement , fixedConstraints , qvarConstraintString .toString () );
149+ exactMatchXQuery = generateSimpleConstraints ( mainElement , true );
150+ generateQvarConstraints ();
151+
152+ return getString ( mainElement , exactMatchXQuery , lengthConstraint , qvarConstraint , qvarMapVariable , header , footer );
174153 }
175154
176155 /**
177- * Builds the XQuery as a string with the set header and footer, given constraint strings and the main element.
156+ * Builds the XQuery as a string given constraint strings, header, footer, qvar map, and the main element.
178157 * @param mainElement Node from which to build XQuery
179158 * @param fixedConstraints Constraint string for basic exact formula matching
159+ * @param lengthConstraint Constraint string for length of variables
180160 * @param qvarConstraintString Constraint string for qvar matching
161+ * @param qvarMapVariable Qvar map variable
162+ * @param header Header
163+ * @param footer Footer
181164 * @return XQuery as string
182165 */
183- public String getString ( Node mainElement , String fixedConstraints , String qvarConstraintString ) {
166+ public static String getString ( Node mainElement , String fixedConstraints , String lengthConstraint ,
167+ String qvarConstraintString , String qvarMapVariable , String header , String footer ) {
184168 String out = header ;
185169 out += "for $x in $m//*:" + getFirstChild ( mainElement ).getLocalName () + "\n " +
186170 fixedConstraints + "\n " ;
187- out += getConstraints ( qvarConstraintString );
188- out +=
189- "return" + "\n " + getFooter ();
171+ if ( !lengthConstraint .isEmpty () || !qvarConstraintString .isEmpty () ) {
172+ out += "where" + "\n " ;
173+ if ( lengthConstraint .isEmpty () ) {
174+ out += qvarConstraintString ;
175+ } else {
176+ out += lengthConstraint + (qvarConstraintString .isEmpty () ? "" : " and " + qvarConstraintString );
177+ }
178+ }
179+ out += qvarMapVariable ;
180+ out += "\n " ;
181+ out += "return" + "\n " + footer ;
190182 return out ;
191183 }
192184
193185 /**
194- * Appends constraint strings together
195- * @param qvarConstraintString Qvar constraint portion of query
196- * @return All constraint strings appended together
186+ * Uses the qvar map to generate a XQuery string containing qvar constraints,
187+ * and the qvar map variable which maps qvar names to their respective formula ID's in the result.
197188 */
198- private String getConstraints ( String qvarConstraintString ) {
199- String out = lengthConstraint +
200- (((qvarConstraintString .length () > 0 ) && (lengthConstraint .length () > 0 )) ? " and " : "" ) +
201- qvarConstraintString ;
202- if ( out .trim ().length () > 0 ) {
203- return "where" + "\n " + out + "\n " ;
204- } else {
205- return "" ;
189+ private void generateQvarConstraints () {
190+ final StringBuilder qvarConstrBuilder = new StringBuilder ();
191+ final StringBuilder qvarMapStrBuilder = new StringBuilder ();
192+ final Iterator <Map .Entry <String , ArrayList <String >>> entryIterator = qvar .entrySet ().iterator ();
193+ if ( entryIterator .hasNext () ) {
194+ qvarMapStrBuilder .append ( "\n let $q := map {" );
195+
196+ while ( entryIterator .hasNext () ) {
197+ final Map .Entry <String , ArrayList <String >> currentEntry = entryIterator .next ();
198+
199+ final Iterator <String > valueIterator = currentEntry .getValue ().iterator ();
200+ final String firstValue = valueIterator .next ();
201+
202+ qvarMapStrBuilder .append ( '"' ).append ( currentEntry .getKey () ).append ( '"' )
203+ .append ( " : (data($x" ).append ( firstValue ).append ( "/@xml:id)" );
204+
205+ //check if there are additional values that we need to constrain
206+ if ( valueIterator .hasNext () ) {
207+ if ( qvarConstrBuilder .length () > 0 ) {
208+ //only add beginning and if it's an additional constraint in the aggregate qvar string
209+ qvarConstrBuilder .append ( "\n and " );
210+ }
211+ while ( valueIterator .hasNext () ) {
212+ //process second value onwards
213+ final String currentValue = valueIterator .next ();
214+ qvarMapStrBuilder .append ( ",data($x" ).append ( currentValue ).append ( "/@xml-id)" );
215+ //These constraints specify that the same qvars must refer to the same nodes,
216+ //using the XQuery "=" equality
217+ //This is equality based on: same text, same node names, and same children nodes
218+ qvarConstrBuilder .append ( "$x" ).append ( firstValue ).append ( " = $x" ).append ( currentValue );
219+ if ( valueIterator .hasNext () ) {
220+ qvarConstrBuilder .append ( " and " );
221+ }
222+ }
223+ }
224+ qvarMapStrBuilder .append ( ')' );
225+ if ( entryIterator .hasNext () ) {
226+ qvarMapStrBuilder .append ( ',' );
227+ }
228+ }
229+ qvarMapStrBuilder .append ( '}' );
206230 }
231+ qvarMapVariable = qvarMapStrBuilder .toString ();
232+ qvarConstraint = qvarConstrBuilder .toString ();
207233 }
208234
209- private String generateConstraint ( Node node ) {
210- return generateConstraints ( node , false );
235+
236+ private String generateSimpleConstraints ( Node node ) {
237+ return generateSimpleConstraints ( node , false );
211238 }
212239
213240 /**
@@ -218,7 +245,7 @@ private String generateConstraint( Node node ) {
218245 * is not added as a constraint here, but in getString())
219246 * @return Exact match XQuery string
220247 */
221- private String generateConstraints ( Node node , boolean isRoot ) {
248+ private String generateSimpleConstraints ( Node node , boolean isRoot ) {
222249 //Index of child node
223250 int childElementIndex = 0 ;
224251 final StringBuilder out = new StringBuilder ();
@@ -264,10 +291,10 @@ private String generateConstraints( Node node, boolean isRoot ) {
264291 //Add relative constraint so this can be recursively called
265292 out .append ( " and *[" ).append ( childElementIndex ).append ( "]" );
266293 }
267- final String constraint = generateConstraint ( child );
294+ final String constraint = generateSimpleConstraints ( child );
268295 if ( !constraint .isEmpty () ) {
269296 //This constraint appears as a child of the relative constraint above (e.g. [*1][constraint])
270- out .append ( "[" ).append ( constraint ).append ( "]" );
297+ out .append ( "[" ).append ( constraint ).append ( "]" );
271298 }
272299 }
273300 }
0 commit comments