/** * Handles info messages resulting from a query execution. * * @param msg info message * @return true if error was found */ private boolean error(final String msg) { final String line = msg.replaceAll("[\\r\\n].*", ""); Matcher m = XQERROR.matcher(line); int el, ec = 2; if (!m.matches()) { m = XMLERROR.matcher(line); if (!m.matches()) return true; el = Integer.parseInt(m.group(1)); errFile = getEditor().file.path(); } else { el = Integer.parseInt(m.group(1)); ec = Integer.parseInt(m.group(2)); errFile = m.group(3); } final EditorArea edit = find(IO.get(errFile), false); if (edit == null) return true; // find approximate error position final int ll = edit.last.length; int ep = ll; for (int e = 1, l = 1, c = 1; e < ll; ++c, e += cl(edit.last, e)) { if (l > el || l == el && c == ec) { ep = e; break; } if (edit.last[e] == '\n') { ++l; c = 0; } } if (ep < ll && Character.isLetterOrDigit(cp(edit.last, ep))) { while (ep > 0 && Character.isLetterOrDigit(cp(edit.last, ep - 1))) ep--; } edit.error(ep); errPos = ep; return true; }
/** * This view allows the input and evaluation of queries and documents. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class EditorView extends View { /** Error string. */ private static final String ERRSTRING = STOPPED_AT + ' ' + (LINE_X + ", " + COLUMN_X).replaceAll("%", "([0-9]+)"); /** XQuery error pattern. */ private static final Pattern XQERROR = Pattern.compile(ERRSTRING + ' ' + IN_FILE_X.replaceAll("%", "(.*?)") + COL); /** XML error pattern. */ private static final Pattern XMLERROR = Pattern.compile(LINE_X.replaceAll("%", "(.*?)") + COL + ".*"); /** History Button. */ final BaseXButton hist; /** Execute Button. */ final BaseXButton stop; /** Info label. */ final BaseXLabel info; /** Position label. */ final BaseXLabel pos; /** Query area. */ final BaseXTabs tabs; /** Execute button. */ final BaseXButton go; /** Thread counter. */ int threadID; /** File in which the most recent error occurred. */ String errFile; /** Most recent error position; used for clicking on error message. */ int errPos; /** Header string. */ private final BaseXLabel header; /** Filter button. */ private final BaseXButton filter; /** Search panel. */ public final SearchPanel search; /** * Default constructor. * * @param man view manager */ public EditorView(final ViewNotifier man) { super(EDITORVIEW, man); if (Prop.langright) applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); border(6, 6, 6, 6).layout(new BorderLayout(0, 2)).setFocusable(false); header = new BaseXLabel(EDITOR, true, false); final BaseXButton srch = new BaseXButton(gui, "search", H_REPLACE); final BaseXButton openB = BaseXButton.command(GUICommands.C_EDITOPEN, gui); final BaseXButton saveB = new BaseXButton(gui, "save", H_SAVE); hist = new BaseXButton(gui, "hist", H_RECENTLY_OPEN); final BaseXBack buttons = new BaseXBack(Fill.NONE); buttons.layout(new TableLayout(1, 4, 1, 0)); buttons.add(srch); buttons.add(openB); buttons.add(saveB); buttons.add(hist); final BaseXBack b = new BaseXBack(Fill.NONE).layout(new BorderLayout(8, 0)); if (Prop.langright) { b.add(header, BorderLayout.EAST); b.add(buttons, BorderLayout.WEST); } else { b.add(header, BorderLayout.CENTER); b.add(buttons, BorderLayout.EAST); } add(b, BorderLayout.NORTH); tabs = new BaseXTabs(gui); tabs.setFocusable(false); final SearchEditor se = new SearchEditor(gui, tabs, null).button(srch); search = se.panel(); addCreateTab(); add(se, BorderLayout.CENTER); // status and query pane search.editor(addTab(), false); info = new BaseXLabel().setText(OK, Msg.SUCCESS); pos = new BaseXLabel(" "); posCode.invokeLater(); stop = new BaseXButton(gui, "stop", H_STOP_PROCESS); stop.addKeyListener(this); stop.setEnabled(false); go = new BaseXButton(gui, "go", H_EXECUTE_QUERY); go.addKeyListener(this); filter = BaseXButton.command(GUICommands.C_FILTER, gui); filter.addKeyListener(this); filter.setEnabled(false); final BaseXBack status = new BaseXBack(Fill.NONE).layout(new BorderLayout(4, 0)); status.add(info, BorderLayout.CENTER); status.add(pos, BorderLayout.EAST); final BaseXBack query = new BaseXBack(Fill.NONE).layout(new TableLayout(1, 3, 1, 0)); query.add(stop); query.add(go); query.add(filter); final BaseXBack south = new BaseXBack(Fill.NONE).border(4, 0, 0, 0); south.layout(new BorderLayout(8, 0)); south.add(status, BorderLayout.CENTER); south.add(query, BorderLayout.EAST); add(south, BorderLayout.SOUTH); refreshLayout(); // add listeners saveB.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { final JPopupMenu pop = new JPopupMenu(); final StringBuilder mnem = new StringBuilder(); final JMenuItem sa = GUIMenu.newItem(GUICommands.C_EDITSAVE, gui, mnem); final JMenuItem sas = GUIMenu.newItem(GUICommands.C_EDITSAVEAS, gui, mnem); GUICommands.C_EDITSAVE.refresh(gui, sa); GUICommands.C_EDITSAVEAS.refresh(gui, sas); pop.add(sa); pop.add(sas); pop.show(saveB, 0, saveB.getHeight()); } }); hist.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { final JPopupMenu pm = new JPopupMenu(); final ActionListener al = new ActionListener() { @Override public void actionPerformed(final ActionEvent ac) { open(new IOFile(ac.getActionCommand())); } }; final StringList sl = new StringList(); for (final EditorArea ea : editors()) sl.add(ea.file.path()); for (final String en : new StringList().add(gui.gprop.strings(GUIProp.EDITOR)).sort(!Prop.WIN, true)) { final JMenuItem it = new JMenuItem(en); it.setEnabled(!sl.contains(en)); pm.add(it).addActionListener(al); } pm.show(hist, 0, hist.getHeight()); } }); refreshHistory(null); info.addMouseListener( new MouseAdapter() { @Override public void mouseClicked(final MouseEvent e) { EditorArea ea = getEditor(); if (errFile != null) { ea = find(IO.get(errFile), false); if (ea == null) ea = open(new IOFile(errFile)); tabs.setSelectedComponent(ea); } if (errPos == -1) return; ea.jumpError(errPos); posCode.invokeLater(); } }); stop.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { stop.setEnabled(false); go.setEnabled(false); gui.stop(); } }); go.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { getEditor().release(Action.EXECUTE); } }); tabs.addChangeListener( new ChangeListener() { @Override public void stateChanged(final ChangeEvent e) { final EditorArea ea = getEditor(); if (ea == null) return; search.editor(ea, true); gui.refreshControls(); posCode.invokeLater(); } }); BaseXLayout.addDrop( this, new DropHandler() { @Override public void drop(final Object file) { if (file instanceof File) open(new IOFile((File) file)); } }); } @Override public void refreshInit() {} @Override public void refreshFocus() {} @Override public void refreshMark() { final EditorArea edit = getEditor(); go.setEnabled(edit.script || edit.xquery && !gui.gprop.is(GUIProp.EXECRT)); final Nodes mrk = gui.context.marked; filter.setEnabled(!gui.gprop.is(GUIProp.FILTERRT) && mrk != null && mrk.size() != 0); } @Override public void refreshContext(final boolean more, final boolean quick) {} @Override public void refreshLayout() { header.setFont(GUIConstants.lfont); for (final EditorArea edit : editors()) edit.setFont(GUIConstants.mfont); search.refreshLayout(); } @Override public void refreshUpdate() {} @Override public boolean visible() { return gui.gprop.is(GUIProp.SHOWEDITOR); } @Override public void visible(final boolean v) { gui.gprop.set(GUIProp.SHOWEDITOR, v); } @Override protected boolean db() { return false; } /** Opens a new file. */ public void open() { // open file chooser for XML creation final BaseXFileChooser fc = new BaseXFileChooser(OPEN, gui.gprop.get(GUIProp.WORKPATH), gui); fc.filter(BXS_FILES, IO.BXSSUFFIX); fc.filter(XQUERY_FILES, IO.XQSUFFIXES); fc.filter(XML_DOCUMENTS, IO.XMLSUFFIXES); final IOFile[] files = fc.multi().selectAll(Mode.FOPEN); for (final IOFile f : files) open(f); } /** Reverts the contents of the currently opened editor. */ public void reopen() { getEditor().reopen(true); } /** * Saves the contents of the currently opened editor. * * @return {@code false} if operation was canceled */ public boolean save() { final EditorArea edit = getEditor(); return edit.opened() ? save(edit.file) : saveAs(); } /** * Saves the contents of the currently opened editor under a new name. * * @return {@code false} if operation was canceled */ public boolean saveAs() { // open file chooser for XML creation final EditorArea edit = getEditor(); final BaseXFileChooser fc = new BaseXFileChooser(SAVE_AS, edit.file.path(), gui).filter(XQUERY_FILES, IO.XQSUFFIXES); final IOFile file = fc.select(Mode.FSAVE); return file != null && save(file); } /** Creates a new file. */ public void newFile() { addTab(); refreshControls(true); } /** * Opens the specified query file. * * @param file query file * @return opened editor */ public EditorArea open(final IOFile file) { if (!visible()) GUICommands.C_SHOWEDITOR.execute(gui); EditorArea edit = find(file, true); try { if (edit != null) { // display open file tabs.setSelectedComponent(edit); edit.reopen(true); } else { // get current editor edit = getEditor(); // create new tab if current text is stored on disk or has been modified if (edit.opened() || edit.modified) edit = addTab(); edit.initText(file.read()); edit.file(file); } } catch (final IOException ex) { BaseXDialog.error(gui, FILE_NOT_OPENED); } return edit; } /** * Refreshes the list of recent query files and updates the query path. * * @param file new file */ void refreshHistory(final IOFile file) { final StringList sl = new StringList(); String path = null; if (file != null) { path = file.path(); gui.gprop.set(GUIProp.WORKPATH, file.dirPath()); sl.add(path); tabs.setToolTipTextAt(tabs.getSelectedIndex(), path); } final String[] qu = gui.gprop.strings(GUIProp.EDITOR); for (int q = 0; q < qu.length && q < 19; q++) { final String f = qu[q]; if (!f.equalsIgnoreCase(path) && IO.get(f).exists()) sl.add(f); } // store sorted history gui.gprop.set(GUIProp.EDITOR, sl.toArray()); hist.setEnabled(!sl.isEmpty()); } /** * Closes an editor. * * @param edit editor to be closed. {@code null} closes the currently opened editor. * @return {@code true} if editor was closed */ public boolean close(final EditorArea edit) { final EditorArea ea = edit != null ? edit : getEditor(); if (!confirm(ea)) return false; tabs.remove(ea); final int t = tabs.getTabCount(); final int i = tabs.getSelectedIndex(); if (t == 1) { // reopen single tab addTab(); } else if (i + 1 == t) { // if necessary, activate last editor tab tabs.setSelectedIndex(i - 1); } return true; } /** Jumps to a specific line. */ public void gotoLine() { final EditorArea edit = getEditor(); final int ll = edit.last.length; final int cr = edit.getCaret(); int l = 1; for (int e = 0; e < ll && e < cr; e += cl(edit.last, e)) { if (edit.last[e] == '\n') ++l; } final DialogLine dl = new DialogLine(gui, l); if (!dl.ok()) return; final int el = dl.line(); int p = 0; l = 1; for (int e = 0; e < ll && l < el; e += cl(edit.last, e)) { if (edit.last[e] != '\n') continue; p = e + 1; ++l; } edit.setCaret(p); posCode.invokeLater(); } /** Starts a thread, which shows a waiting info after a short timeout. */ public void start() { final int thread = threadID; new Thread() { @Override public void run() { Performance.sleep(200); if (thread == threadID) { info.setText(PLEASE_WAIT_D, Msg.SUCCESS).setToolTipText(null); stop.setEnabled(true); } } }.start(); } /** * Evaluates the info message resulting from a parsed or executed query. * * @param msg info message * @param ok {@code true} if evaluation was successful * @param up update */ public void info(final String msg, final boolean ok, final boolean up) { ++threadID; errPos = -1; errFile = null; getEditor().resetError(); final String m = msg.replaceAll("^.*\r?\n\\[.*?\\]", "") .replaceAll(".*" + LINE_X.replaceAll("%", ".*?") + COL, ""); if (ok) { info.setCursor(GUIConstants.CURSORARROW); info.setText(m, Msg.SUCCESS).setToolTipText(null); } else { info.setCursor(error(msg) ? GUIConstants.CURSORHAND : GUIConstants.CURSORARROW); info.setText(m, Msg.ERROR).setToolTipText(msg); } if (up) { stop.setEnabled(false); refreshMark(); } } /** * Handles info messages resulting from a query execution. * * @param msg info message * @return true if error was found */ private boolean error(final String msg) { final String line = msg.replaceAll("[\\r\\n].*", ""); Matcher m = XQERROR.matcher(line); int el, ec = 2; if (!m.matches()) { m = XMLERROR.matcher(line); if (!m.matches()) return true; el = Integer.parseInt(m.group(1)); errFile = getEditor().file.path(); } else { el = Integer.parseInt(m.group(1)); ec = Integer.parseInt(m.group(2)); errFile = m.group(3); } final EditorArea edit = find(IO.get(errFile), false); if (edit == null) return true; // find approximate error position final int ll = edit.last.length; int ep = ll; for (int e = 1, l = 1, c = 1; e < ll; ++c, e += cl(edit.last, e)) { if (l > el || l == el && c == ec) { ep = e; break; } if (edit.last[e] == '\n') { ++l; c = 0; } } if (ep < ll && Character.isLetterOrDigit(cp(edit.last, ep))) { while (ep > 0 && Character.isLetterOrDigit(cp(edit.last, ep - 1))) ep--; } edit.error(ep); errPos = ep; return true; } /** * Shows a quit dialog for all modified query files. * * @return {@code false} if confirmation was canceled */ public boolean confirm() { for (final EditorArea edit : editors()) { tabs.setSelectedComponent(edit); if (!close(edit)) return false; } return true; } /** * Checks if the current text can be saved or reverted. * * @param rev revert flag * @return result of check */ public boolean modified(final boolean rev) { final EditorArea edit = getEditor(); return edit.modified || !rev && !edit.opened(); } /** * Returns the current editor. * * @return editor */ public EditorArea getEditor() { final Component c = tabs.getSelectedComponent(); return c instanceof EditorArea ? (EditorArea) c : null; } /** * Refreshes the query modification flag. * * @param force action */ void refreshControls(final boolean force) { // update modification flag final EditorArea edit = getEditor(); final boolean oe = edit.modified; edit.modified = edit.hist != null && edit.hist.modified(); if (edit.modified == oe && !force) return; // update tab title String title = edit.file.name(); if (edit.modified) title += '*'; edit.label.setText(title); // update components gui.refreshControls(); posCode.invokeLater(); } /** Code for setting cursor position. */ final GUICode posCode = new GUICode() { @Override public void eval(final Object arg) { final int[] lc = getEditor().pos(); pos.setText(lc[0] + " : " + lc[1]); } }; /** * Finds the editor that contains the specified file. * * @param file file to be found * @param opened considers only opened files * @return editor */ EditorArea find(final IO file, final boolean opened) { for (final EditorArea edit : editors()) { if (edit.file.eq(file) && (!opened || edit.opened())) return edit; } return null; } /** * Saves the specified editor contents. * * @param file file to write * @return {@code false} if confirmation was canceled */ private boolean save(final IOFile file) { try { final EditorArea edit = getEditor(); file.write(edit.getText()); edit.file(file); return true; } catch (final IOException ex) { BaseXDialog.error(gui, FILE_NOT_SAVED); return false; } } /** * Choose a unique tab file. * * @return io reference */ private IOFile newTabFile() { // collect numbers of existing files final BoolList bl = new BoolList(); for (final EditorArea edit : editors()) { if (edit.opened()) continue; final String n = edit.file.name().substring(FILE.length()); bl.set(n.isEmpty() ? 1 : Integer.parseInt(n), true); } // find first free file number int c = 0; while (++c < bl.size() && bl.get(c)) ; // create io reference return new IOFile(gui.gprop.get(GUIProp.WORKPATH), FILE + (c == 1 ? "" : c)); } /** * Adds a new editor tab. * * @return editor reference */ EditorArea addTab() { final EditorArea edit = new EditorArea(this, newTabFile()); edit.setFont(GUIConstants.mfont); final BaseXBack tab = new BaseXBack(new BorderLayout(10, 0)).mode(Fill.NONE); tab.add(edit.label, BorderLayout.CENTER); final BaseXButton close = tabButton("e_close"); close.setRolloverIcon(BaseXLayout.icon("e_close2")); close.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { close(edit); } }); tab.add(close, BorderLayout.EAST); tabs.add(edit, tab, tabs.getComponentCount() - 2); return edit; } /** Adds a tab for creating new tabs. */ private void addCreateTab() { final BaseXButton add = tabButton("e_new"); add.setRolloverIcon(BaseXLayout.icon("e_new2")); add.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { addTab(); refreshControls(true); } }); tabs.add(new BaseXBack(), add, 0); tabs.setEnabledAt(0, false); } /** * Adds a new tab button. * * @param icon button icon * @return button */ private BaseXButton tabButton(final String icon) { final BaseXButton b = new BaseXButton(gui, icon, null); b.border(2, 2, 2, 2).setContentAreaFilled(false); b.setFocusable(false); return b; } /** * Shows a quit dialog for the specified editor. * * @param edit editor to be saved * @return {@code false} if confirmation was canceled */ private boolean confirm(final EditorArea edit) { if (edit.modified && (edit.opened() || edit.getText().length != 0)) { final Boolean ok = BaseXDialog.yesNoCancel(gui, Util.info(CLOSE_FILE_X, edit.file.name())); if (ok == null || ok && !save()) return false; } return true; } /** * Returns all editors. * * @return editors */ EditorArea[] editors() { final ArrayList<EditorArea> edits = new ArrayList<EditorArea>(); for (final Component c : tabs.getComponents()) { if (c instanceof EditorArea) edits.add((EditorArea) c); } return edits.toArray(new EditorArea[edits.size()]); } }
/** * This class is an entry point for evaluating XQuery strings. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ public final class QueryProcessor extends Proc implements AutoCloseable { /** Pattern for detecting library modules. */ private static final Pattern LIBMOD_PATTERN = Pattern.compile( "^(xquery( version ['\"].*?['\"])?( encoding ['\"].*?['\"])? ?; ?)?module namespace.*"); /** Static context. */ public final StaticContext sc; /** Expression context. */ public final QueryContext qc; /** Query. */ private final String query; /** Parsed flag. */ private boolean parsed; /** * Default constructor. * * @param query query string * @param ctx database context */ public QueryProcessor(final String query, final Context ctx) { this.query = query; qc = proc(new QueryContext(ctx)); sc = new StaticContext(ctx); } /** * Parses the query. * * @throws QueryException query exception */ public void parse() throws QueryException { if (parsed) return; parsed = true; qc.parseMain(query, null, sc); updating = qc.updating; } /** * Compiles the query. * * @throws QueryException query exception */ public void compile() throws QueryException { parse(); qc.compile(); } /** * Returns a result iterator. * * @return result iterator * @throws QueryException query exception */ public Iter iter() throws QueryException { parse(); return qc.iter(); } /** * Returns a result value. * * @return result value * @throws QueryException query exception */ public Value value() throws QueryException { parse(); return qc.iter().value(); } /** * Evaluates the specified query and returns the result. * * @return result of query * @throws QueryException query exception */ public Result execute() throws QueryException { parse(); return qc.execute(); } /** * Binds a value with the specified type to a global variable. If the value is an {@link Expr} * instance, it is directly assigned. Otherwise, it is first cast to the appropriate XQuery type. * If {@code "json"} is specified as type, the value is interpreted according to the rules * specified in {@link JsonMapConverter}. * * @param name name of variable * @param value value to be bound * @param type type (may be {@code null}) * @return self reference * @throws QueryException query exception */ public QueryProcessor bind(final String name, final Object value, final String type) throws QueryException { qc.bind(name, value, type, sc); return this; } /** * Binds a value to a global variable. * * @param name name of variable * @param value value to be bound * @return self reference * @throws QueryException query exception */ public QueryProcessor bind(final String name, final Object value) throws QueryException { return bind(name, value, null); } /** * Binds an XQuery value to a global variable. * * @param name name of variable * @param value value to be bound * @return self reference * @throws QueryException query exception */ public QueryProcessor bind(final String name, final Value value) throws QueryException { qc.bind(name, value, sc); return this; } /** * Binds the context value. * * @param value value to be bound * @return self reference * @throws QueryException query exception */ public QueryProcessor context(final Object value) throws QueryException { return context(value, null); } /** * Binds the context value. * * @param value XQuery value to be bound * @return self reference */ public QueryProcessor context(final Value value) { qc.context(value, sc); return this; } /** * Binds the HTTP context to the query processor. * * @param value HTTP context * @return self reference */ public QueryProcessor http(final Object value) { qc.http(value); return this; } /** * Binds the context value with a specified type, using the same rules as for {@link #bind binding * variables}. * * @param value value to be bound * @param type type (may be {@code null}) * @return self reference * @throws QueryException query exception */ public QueryProcessor context(final Object value, final String type) throws QueryException { qc.context(value, type, sc); return this; } /** * Declares a namespace. A namespace is undeclared if the {@code uri} is an empty string. The * default element namespaces is set if the {@code prefix} is empty. * * @param prefix namespace prefix * @param uri namespace uri * @return self reference * @throws QueryException query exception */ public QueryProcessor namespace(final String prefix, final String uri) throws QueryException { sc.namespace(prefix, uri); return this; } /** * Returns a serializer for the given output stream. Optional output declarations within the query * will be included in the serializer instance. * * @param os output stream * @return serializer instance * @throws IOException query exception * @throws QueryException query exception */ public Serializer getSerializer(final OutputStream os) throws IOException, QueryException { compile(); try { return Serializer.get(os, qc.serParams()); } catch (final QueryIOException ex) { throw ex.getCause(); } } /** * Adds a module reference. Only called from the test APIs. * * @param uri module uri * @param file file name */ public void module(final String uri, final String file) { qc.modDeclared.put(uri, file); } /** * Returns the query string. * * @return query */ public String query() { return query; } @Override public void close() { qc.close(); } @Override public void databases(final LockResult lr) { qc.databases(lr); } /** * Returns the number of performed updates after query execution, or {@code 0}. * * @return number of updates */ public int updates() { return updating ? qc.resources.updates().size() : 0; } /** * Returns query information. * * @return query information */ public String info() { return qc.info(); } /** * Checks if the specified XQuery string is a library module. * * @param qu query string * @return result of check */ public static boolean isLibrary(final String qu) { return LIBMOD_PATTERN.matcher(removeComments(qu, 80)).matches(); } /** * Removes comments from the specified string and returns the first characters of a query. * * @param qu query string * @param max maximum length of string to return * @return result */ public static String removeComments(final String qu, final int max) { final StringBuilder sb = new StringBuilder(); int m = 0; boolean s = false; final int cl = qu.length(); for (int c = 0; c < cl && sb.length() < max; ++c) { final char ch = qu.charAt(c); if (ch == 0x0d) continue; if (ch == '(' && c + 1 < cl && qu.charAt(c + 1) == ':') { if (m == 0 && !s) { sb.append(' '); s = true; } ++m; ++c; } else if (m != 0 && ch == ':' && c + 1 < cl && qu.charAt(c + 1) == ')') { --m; ++c; } else if (m == 0) { if (ch > ' ') sb.append(ch); else if (!s) sb.append(' '); s = ch <= ' '; } } if (sb.length() >= max) sb.append("..."); return sb.toString().trim(); } /** * Returns a map with variable bindings. * * @param opts main options * @return bindings */ public static HashMap<String, String> bindings(final MainOptions opts) { final HashMap<String, String> bindings = new HashMap<>(); final String bind = opts.get(MainOptions.BINDINGS).trim(); final StringBuilder key = new StringBuilder(); final StringBuilder val = new StringBuilder(); boolean first = true; final int sl = bind.length(); for (int s = 0; s < sl; s++) { final char ch = bind.charAt(s); if (first) { if (ch == '=') { first = false; } else { key.append(ch); } } else { if (ch == ',') { if (s + 1 == sl || bind.charAt(s + 1) != ',') { bindings.put(key.toString().trim(), val.toString()); key.setLength(0); val.setLength(0); first = true; continue; } // literal commas are escaped by a second comma s++; } val.append(ch); } } if (key.length() != 0) bindings.put(key.toString().trim(), val.toString()); return bindings; } /** * Returns a tree representation of the query plan. * * @return root node */ public FDoc plan() { return new FDoc().add(qc.plan()); } @Override public String tit() { return PLEASE_WAIT_D; } @Override public String det() { return PLEASE_WAIT_D; } @Override public String toString() { return query; } }
/** * Checks if the specified XQuery string is a library module. * * @param qu query string * @return result of check */ public static boolean isLibrary(final String qu) { return LIBMOD_PATTERN.matcher(removeComments(qu, 80)).matches(); }
/** * This class is an entry point for evaluating XQuery strings. * * @author BaseX Team 2005-16, BSD License * @author Christian Gruen */ public final class QueryProcessor extends Job implements Closeable { /** Pattern for detecting library modules. */ private static final Pattern LIBMOD_PATTERN = Pattern.compile( "^(xquery( version ['\"].*?['\"])?( encoding ['\"].*?['\"])? ?; ?)?module namespace.*"); /** Static context. */ public final StaticContext sc; /** Expression context. */ public final QueryContext qc; /** Query. */ private final String query; /** Parsed flag. */ private boolean parsed; /** * Default constructor. * * @param query query string * @param ctx database context */ public QueryProcessor(final String query, final Context ctx) { this(query, null, ctx); } /** * Default constructor. * * @param query query string * @param uri base uri (can be {@code null}) * @param ctx database context */ public QueryProcessor(final String query, final String uri, final Context ctx) { this.query = query; qc = pushJob(new QueryContext(ctx)); sc = new StaticContext(qc); sc.baseURI(uri); } /** * Parses the query. * * @throws QueryException query exception */ public void parse() throws QueryException { if (parsed) return; try { qc.parseMain(query, null, sc); } finally { parsed = true; updating = qc.updating; } } /** * Compiles the query. * * @throws QueryException query exception */ public void compile() throws QueryException { parse(); qc.compile(); } /** * Returns a memory-efficient result iterator. In most cases, the query will only be fully * evaluated if all items of this iterator are requested. * * @return result iterator * @throws QueryException query exception */ public Iter iter() throws QueryException { parse(); return qc.iter(); } /** * Evaluates the query and returns the resulting value. * * @return result value * @throws QueryException query exception */ public Value value() throws QueryException { parse(); return qc.iter().value(); } /** * This function is called by the GUI; use {@link #iter()} or {@link #value()} instead. Caches and * returns the result of the specified query. If all nodes are of the same database instance, the * returned value will be of type {@link DBNodes}. * * @param max maximum number of results to cache (negative: return all values) * @return result of query * @throws QueryException query exception */ public Value cache(final int max) throws QueryException { parse(); return qc.cache(max); } /** * Binds a value with the specified type to a global variable. If the value is an {@link Expr} * instance, it is directly assigned. Otherwise, it is first cast to the appropriate XQuery type. * If {@code "json"} is specified as type, the value is interpreted according to the rules * specified in {@link JsonMapConverter}. * * @param name name of variable * @param value value to be bound * @param type type (may be {@code null}) * @return self reference * @throws QueryException query exception */ public QueryProcessor bind(final String name, final Object value, final String type) throws QueryException { qc.bind(name, value, type, sc); return this; } /** * Binds a value to a global variable. * * @param name name of variable * @param value value to be bound * @return self reference * @throws QueryException query exception */ public QueryProcessor bind(final String name, final Object value) throws QueryException { return bind(name, value, null); } /** * Binds an XQuery value to a global variable. * * @param name name of variable * @param value value to be bound * @return self reference * @throws QueryException query exception */ public QueryProcessor bind(final String name, final Value value) throws QueryException { qc.bind(name, value, sc); return this; } /** * Binds the context value. * * @param value value to be bound * @return self reference * @throws QueryException query exception */ public QueryProcessor context(final Object value) throws QueryException { return context(value, null); } /** * Binds the context value. * * @param value XQuery value to be bound * @return self reference */ public QueryProcessor context(final Value value) { qc.context(value, sc); return this; } /** * Binds the HTTP context to the query processor. * * @param value HTTP context * @return self reference */ public QueryProcessor http(final Object value) { qc.http(value); return this; } /** * Binds the context value with a specified type, using the same rules as for {@link #bind binding * variables}. * * @param value value to be bound * @param type type (may be {@code null}) * @return self reference * @throws QueryException query exception */ public QueryProcessor context(final Object value, final String type) throws QueryException { qc.context(value, type, sc); return this; } /** * Declares a namespace. A namespace is undeclared if the {@code uri} is an empty string. The * default element namespaces is set if the {@code prefix} is empty. * * @param prefix namespace prefix * @param uri namespace uri * @return self reference * @throws QueryException query exception */ public QueryProcessor namespace(final String prefix, final String uri) throws QueryException { sc.namespace(prefix, uri); return this; } /** * Assigns a URI resolver. * * @param resolver resolver * @return self reference */ public QueryProcessor uriResolver(final UriResolver resolver) { sc.resolver = resolver; return this; } /** * Returns a serializer for the given output stream. Optional output declarations within the query * will be included in the serializer instance. * * @param os output stream * @return serializer instance * @throws IOException query exception * @throws QueryException query exception */ public Serializer getSerializer(final OutputStream os) throws IOException, QueryException { compile(); try { return Serializer.get(os, qc.serParams()).sc(sc); } catch (final QueryIOException ex) { throw ex.getCause(); } } /** * Adds a module reference. Only called from the test APIs. * * @param uri module uri * @param file file name */ public void module(final String uri, final String file) { qc.modDeclared.put(uri, file); } /** * Returns the query string. * * @return query */ public String query() { return query; } @Override public void close() { qc.close(); } @Override public void databases(final LockResult lr) { qc.databases(lr); } /** * Returns the number of performed updates after query execution, or {@code 0}. * * @return number of updates */ public int updates() { return updating ? qc.updates().size() : 0; } /** * Returns query information. * * @return query information */ public String info() { return qc.info(); } /** * Checks if the specified XQuery string is a library module. * * @param qu query string * @return result of check */ public static boolean isLibrary(final String qu) { return LIBMOD_PATTERN.matcher(removeComments(qu, 80)).matches(); } /** * Removes comments from the specified string and returns the first characters of a query. * * @param qu query string * @param max maximum length of string to return * @return result */ public static String removeComments(final String qu, final int max) { final StringBuilder sb = new StringBuilder(); int m = 0; boolean s = false; final int cl = qu.length(); for (int c = 0; c < cl && sb.length() < max; ++c) { final char ch = qu.charAt(c); if (ch == 0x0d) continue; if (ch == '(' && c + 1 < cl && qu.charAt(c + 1) == ':') { if (m == 0 && !s) { sb.append(' '); s = true; } ++m; ++c; } else if (m != 0 && ch == ':' && c + 1 < cl && qu.charAt(c + 1) == ')') { --m; ++c; } else if (m == 0) { if (ch > ' ') sb.append(ch); else if (!s) sb.append(' '); s = ch <= ' '; } } if (sb.length() >= max) sb.append("..."); return sb.toString().trim(); } /** * Returns a tree representation of the query plan. * * @return root node */ public FDoc plan() { return new FDoc().add(qc.plan()); } @Override public String shortInfo() { return PLEASE_WAIT_D; } @Override public String detailedInfo() { return PLEASE_WAIT_D; } @Override public String toString() { return query; } }