001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.ByteArrayInputStream; 007import java.io.CharArrayReader; 008import java.io.CharArrayWriter; 009import java.io.File; 010import java.io.IOException; 011import java.io.InputStream; 012import java.nio.charset.StandardCharsets; 013import java.nio.file.Files; 014import java.nio.file.InvalidPathException; 015import java.util.ArrayList; 016import java.util.Collection; 017import java.util.Collections; 018import java.util.HashMap; 019import java.util.HashSet; 020import java.util.List; 021import java.util.Locale; 022import java.util.Map; 023import java.util.Set; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026 027import javax.script.ScriptEngine; 028import javax.script.ScriptException; 029import javax.swing.JOptionPane; 030import javax.swing.SwingUtilities; 031import javax.xml.parsers.DocumentBuilder; 032import javax.xml.parsers.ParserConfigurationException; 033import javax.xml.stream.XMLStreamException; 034import javax.xml.transform.OutputKeys; 035import javax.xml.transform.Transformer; 036import javax.xml.transform.TransformerException; 037import javax.xml.transform.TransformerFactoryConfigurationError; 038import javax.xml.transform.dom.DOMSource; 039import javax.xml.transform.stream.StreamResult; 040 041import org.openstreetmap.josm.Main; 042import org.openstreetmap.josm.data.Preferences; 043import org.openstreetmap.josm.data.PreferencesUtils; 044import org.openstreetmap.josm.data.Version; 045import org.openstreetmap.josm.gui.MainApplication; 046import org.openstreetmap.josm.plugins.PluginDownloadTask; 047import org.openstreetmap.josm.plugins.PluginInformation; 048import org.openstreetmap.josm.plugins.ReadLocalPluginInformationTask; 049import org.openstreetmap.josm.spi.preferences.Config; 050import org.openstreetmap.josm.spi.preferences.Setting; 051import org.openstreetmap.josm.tools.LanguageInfo; 052import org.openstreetmap.josm.tools.Logging; 053import org.openstreetmap.josm.tools.Utils; 054import org.openstreetmap.josm.tools.XmlUtils; 055import org.w3c.dom.DOMException; 056import org.w3c.dom.Document; 057import org.w3c.dom.Element; 058import org.w3c.dom.Node; 059import org.w3c.dom.NodeList; 060import org.xml.sax.SAXException; 061 062/** 063 * Class to process configuration changes stored in XML 064 * can be used to modify preferences, store/delete files in .josm folders etc 065 */ 066public final class CustomConfigurator { 067 068 private CustomConfigurator() { 069 // Hide default constructor for utils classes 070 } 071 072 /** 073 * Read configuration script from XML file, modifying main preferences 074 * @param dir - directory 075 * @param fileName - XML file name 076 */ 077 public static void readXML(String dir, String fileName) { 078 readXML(new File(dir, fileName)); 079 } 080 081 /** 082 * Read configuration script from XML file, modifying given preferences object 083 * @param file - file to open for reading XML 084 * @param prefs - arbitrary Preferences object to modify by script 085 */ 086 public static void readXML(final File file, final Preferences prefs) { 087 synchronized (CustomConfigurator.class) { 088 busy = true; 089 } 090 new XMLCommandProcessor(prefs).openAndReadXML(file); 091 synchronized (CustomConfigurator.class) { 092 CustomConfigurator.class.notifyAll(); 093 busy = false; 094 } 095 } 096 097 /** 098 * Read configuration script from XML file, modifying main preferences 099 * @param file - file to open for reading XML 100 */ 101 public static void readXML(File file) { 102 readXML(file, Main.pref); 103 } 104 105 /** 106 * Downloads file to one of JOSM standard folders 107 * @param address - URL to download 108 * @param path - file path relative to base where to put downloaded file 109 * @param base - only "prefs", "cache" and "plugins" allowed for standard folders 110 */ 111 public static void downloadFile(String address, String path, String base) { 112 processDownloadOperation(address, path, getDirectoryByAbbr(base), true, false); 113 } 114 115 /** 116 * Downloads file to one of JOSM standard folders and unpack it as ZIP/JAR file 117 * @param address - URL to download 118 * @param path - file path relative to base where to put downloaded file 119 * @param base - only "prefs", "cache" and "plugins" allowed for standard folders 120 */ 121 public static void downloadAndUnpackFile(String address, String path, String base) { 122 processDownloadOperation(address, path, getDirectoryByAbbr(base), true, true); 123 } 124 125 /** 126 * Downloads file to arbitrary folder 127 * @param address - URL to download 128 * @param path - file path relative to parentDir where to put downloaded file 129 * @param parentDir - folder where to put file 130 * @param mkdir - if true, non-existing directories will be created 131 * @param unzip - if true file wil be unzipped and deleted after download 132 */ 133 public static void processDownloadOperation(String address, String path, String parentDir, boolean mkdir, boolean unzip) { 134 String dir = parentDir; 135 if (path.contains("..") || path.startsWith("/") || path.contains(":")) { 136 return; // some basic protection 137 } 138 File fOut = new File(dir, path); 139 DownloadFileTask downloadFileTask = new DownloadFileTask(Main.parent, address, fOut, mkdir, unzip); 140 141 MainApplication.worker.submit(downloadFileTask); 142 PreferencesUtils.log("Info: downloading file from %s to %s in background ", parentDir, fOut.getAbsolutePath()); 143 if (unzip) PreferencesUtils.log("and unpacking it"); else PreferencesUtils.log(""); 144 145 } 146 147 /** 148 * Simple function to show messageBox, may be used from JS API and from other code 149 * @param type - 'i','w','e','q','p' for Information, Warning, Error, Question, Message 150 * @param text - message to display, HTML allowed 151 */ 152 public static void messageBox(String type, String text) { 153 char c = (type == null || type.isEmpty() ? "plain" : type).charAt(0); 154 switch (c) { 155 case 'i': JOptionPane.showMessageDialog(Main.parent, text, tr("Information"), JOptionPane.INFORMATION_MESSAGE); break; 156 case 'w': JOptionPane.showMessageDialog(Main.parent, text, tr("Warning"), JOptionPane.WARNING_MESSAGE); break; 157 case 'e': JOptionPane.showMessageDialog(Main.parent, text, tr("Error"), JOptionPane.ERROR_MESSAGE); break; 158 case 'q': JOptionPane.showMessageDialog(Main.parent, text, tr("Question"), JOptionPane.QUESTION_MESSAGE); break; 159 case 'p': JOptionPane.showMessageDialog(Main.parent, text, tr("Message"), JOptionPane.PLAIN_MESSAGE); break; 160 default: Logging.warn("Unsupported messageBox type: " + c); 161 } 162 } 163 164 /** 165 * Simple function for choose window, may be used from JS API and from other code 166 * @param text - message to show, HTML allowed 167 * @param opts - 168 * @return number of pressed button, -1 if cancelled 169 */ 170 public static int askForOption(String text, String opts) { 171 if (!opts.isEmpty()) { 172 return JOptionPane.showOptionDialog(Main.parent, text, "Question", 173 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, opts.split(";"), 0); 174 } else { 175 return JOptionPane.showOptionDialog(Main.parent, text, "Question", 176 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, 2); 177 } 178 } 179 180 public static String askForText(String text) { 181 String s = JOptionPane.showInputDialog(Main.parent, text, tr("Enter text"), JOptionPane.QUESTION_MESSAGE); 182 return s != null ? s.trim() : null; 183 } 184 185 /** 186 * This function exports part of user preferences to specified file. 187 * Default values are not saved. 188 * @param filename - where to export 189 * @param append - if true, resulting file cause appending to exuisting preferences 190 * @param keys - which preferences keys you need to export ("imagery.entries", for example) 191 */ 192 public static void exportPreferencesKeysToFile(String filename, boolean append, String... keys) { 193 Set<String> keySet = new HashSet<>(); 194 Collections.addAll(keySet, keys); 195 exportPreferencesKeysToFile(filename, append, keySet); 196 } 197 198 /** 199 * This function exports part of user preferences to specified file. 200 * Default values are not saved. 201 * Preference keys matching specified pattern are saved 202 * @param fileName - where to export 203 * @param append - if true, resulting file cause appending to exuisting preferences 204 * @param pattern - Regexp pattern forh preferences keys you need to export (".*imagery.*", for example) 205 */ 206 public static void exportPreferencesKeysByPatternToFile(String fileName, boolean append, String pattern) { 207 List<String> keySet = new ArrayList<>(); 208 Map<String, Setting<?>> allSettings = Main.pref.getAllSettings(); 209 for (String key: allSettings.keySet()) { 210 if (key.matches(pattern)) 211 keySet.add(key); 212 } 213 exportPreferencesKeysToFile(fileName, append, keySet); 214 } 215 216 /** 217 * Export specified preferences keys to configuration file 218 * @param filename - name of file 219 * @param append - will the preferences be appended to existing ones when file is imported later. 220 * Elsewhere preferences from file will replace existing keys. 221 * @param keys - collection of preferences key names to save 222 */ 223 public static void exportPreferencesKeysToFile(String filename, boolean append, Collection<String> keys) { 224 Element root = null; 225 Document document = null; 226 Document exportDocument = null; 227 228 try { 229 String toXML = Main.pref.toXML(true); 230 DocumentBuilder builder = XmlUtils.newSafeDOMBuilder(); 231 document = builder.parse(new ByteArrayInputStream(toXML.getBytes(StandardCharsets.UTF_8))); 232 exportDocument = builder.newDocument(); 233 root = document.getDocumentElement(); 234 } catch (SAXException | IOException | ParserConfigurationException ex) { 235 Logging.log(Logging.LEVEL_WARN, "Error getting preferences to save:", ex); 236 } 237 if (root == null || exportDocument == null) 238 return; 239 try { 240 Element newRoot = exportDocument.createElement("config"); 241 exportDocument.appendChild(newRoot); 242 243 Element prefElem = exportDocument.createElement("preferences"); 244 prefElem.setAttribute("operation", append ? "append" : "replace"); 245 newRoot.appendChild(prefElem); 246 247 NodeList childNodes = root.getChildNodes(); 248 int n = childNodes.getLength(); 249 for (int i = 0; i < n; i++) { 250 Node item = childNodes.item(i); 251 if (item.getNodeType() == Node.ELEMENT_NODE) { 252 String currentKey = ((Element) item).getAttribute("key"); 253 if (keys.contains(currentKey)) { 254 Node imported = exportDocument.importNode(item, true); 255 prefElem.appendChild(imported); 256 } 257 } 258 } 259 File f = new File(filename); 260 Transformer ts = XmlUtils.newSafeTransformerFactory().newTransformer(); 261 ts.setOutputProperty(OutputKeys.INDENT, "yes"); 262 ts.transform(new DOMSource(exportDocument), new StreamResult(f.toURI().getPath())); 263 } catch (DOMException | TransformerFactoryConfigurationError | TransformerException ex) { 264 Logging.warn("Error saving preferences part:"); 265 Logging.error(ex); 266 } 267 } 268 269 public static void deleteFile(String path, String base) { 270 String dir = getDirectoryByAbbr(base); 271 if (dir == null) { 272 PreferencesUtils.log("Error: Can not find base, use base=cache, base=prefs or base=plugins attribute."); 273 return; 274 } 275 PreferencesUtils.log("Delete file: %s\n", path); 276 if (path.contains("..") || path.startsWith("/") || path.contains(":")) { 277 return; // some basic protection 278 } 279 File fOut = new File(dir, path); 280 if (fOut.exists()) { 281 deleteFileOrDirectory(fOut); 282 } 283 } 284 285 public static void deleteFileOrDirectory(File f) { 286 if (f.isDirectory()) { 287 File[] files = f.listFiles(); 288 if (files != null) { 289 for (File f1: files) { 290 deleteFileOrDirectory(f1); 291 } 292 } 293 } 294 if (!Utils.deleteFile(f)) { 295 PreferencesUtils.log("Warning: Can not delete file "+f.getPath()); 296 } 297 } 298 299 private static boolean busy; 300 301 public static void pluginOperation(String install, String uninstall, String delete) { 302 final List<String> installList = new ArrayList<>(); 303 final List<String> removeList = new ArrayList<>(); 304 final List<String> deleteList = new ArrayList<>(); 305 Collections.addAll(installList, install.toLowerCase(Locale.ENGLISH).split(";")); 306 Collections.addAll(removeList, uninstall.toLowerCase(Locale.ENGLISH).split(";")); 307 Collections.addAll(deleteList, delete.toLowerCase(Locale.ENGLISH).split(";")); 308 installList.remove(""); 309 removeList.remove(""); 310 deleteList.remove(""); 311 312 if (!installList.isEmpty()) { 313 PreferencesUtils.log("Plugins install: "+installList); 314 } 315 if (!removeList.isEmpty()) { 316 PreferencesUtils.log("Plugins turn off: "+removeList); 317 } 318 if (!deleteList.isEmpty()) { 319 PreferencesUtils.log("Plugins delete: "+deleteList); 320 } 321 322 final ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(); 323 Runnable r = () -> { 324 if (task.isCanceled()) return; 325 synchronized (CustomConfigurator.class) { 326 try { // proceed only after all other tasks were finished 327 while (busy) CustomConfigurator.class.wait(); 328 } catch (InterruptedException ex) { 329 Logging.log(Logging.LEVEL_WARN, "InterruptedException while reading local plugin information", ex); 330 Thread.currentThread().interrupt(); 331 } 332 333 SwingUtilities.invokeLater(() -> { 334 List<PluginInformation> availablePlugins = task.getAvailablePlugins(); 335 List<PluginInformation> toInstallPlugins = new ArrayList<>(); 336 List<PluginInformation> toRemovePlugins = new ArrayList<>(); 337 List<PluginInformation> toDeletePlugins = new ArrayList<>(); 338 for (PluginInformation pi1: availablePlugins) { 339 String name = pi1.name.toLowerCase(Locale.ENGLISH); 340 if (installList.contains(name)) toInstallPlugins.add(pi1); 341 if (removeList.contains(name)) toRemovePlugins.add(pi1); 342 if (deleteList.contains(name)) toDeletePlugins.add(pi1); 343 } 344 if (!installList.isEmpty()) { 345 PluginDownloadTask pluginDownloadTask = 346 new PluginDownloadTask(Main.parent, toInstallPlugins, tr("Installing plugins")); 347 MainApplication.worker.submit(pluginDownloadTask); 348 } 349 List<String> pls = new ArrayList<>(Config.getPref().getList("plugins")); 350 for (PluginInformation pi2: toInstallPlugins) { 351 if (!pls.contains(pi2.name)) { 352 pls.add(pi2.name); 353 } 354 } 355 for (PluginInformation pi3: toRemovePlugins) { 356 pls.remove(pi3.name); 357 } 358 for (PluginInformation pi4: toDeletePlugins) { 359 pls.remove(pi4.name); 360 new File(Main.pref.getPluginsDirectory(), pi4.name+".jar").deleteOnExit(); 361 } 362 Config.getPref().putList("plugins", pls); 363 }); 364 } 365 }; 366 MainApplication.worker.submit(task); 367 MainApplication.worker.submit(r); 368 } 369 370 private static String getDirectoryByAbbr(String base) { 371 String dir; 372 if ("prefs".equals(base) || base.isEmpty()) { 373 dir = Config.getDirs().getPreferencesDirectory(false).getAbsolutePath(); 374 } else if ("cache".equals(base)) { 375 dir = Config.getDirs().getCacheDirectory(false).getAbsolutePath(); 376 } else if ("plugins".equals(base)) { 377 dir = Main.pref.getPluginsDirectory().getAbsolutePath(); 378 } else { 379 dir = null; 380 } 381 return dir; 382 } 383 384 public static class XMLCommandProcessor { 385 386 private Preferences mainPrefs; 387 private final Map<String, Element> tasksMap = new HashMap<>(); 388 389 private boolean lastV; // last If condition result 390 391 private ScriptEngine engine; 392 393 public void openAndReadXML(File file) { 394 PreferencesUtils.log("-- Reading custom preferences from " + file.getAbsolutePath() + " --"); 395 try { 396 String fileDir = file.getParentFile().getAbsolutePath(); 397 if (fileDir != null) engine.eval("scriptDir='"+normalizeDirName(fileDir) +"';"); 398 try (InputStream is = Files.newInputStream(file.toPath())) { 399 openAndReadXML(is); 400 } 401 } catch (ScriptException | IOException | SecurityException | InvalidPathException ex) { 402 PreferencesUtils.log(ex, "Error reading custom preferences:"); 403 } 404 } 405 406 public void openAndReadXML(InputStream is) { 407 try { 408 Document document = XmlUtils.parseSafeDOM(is); 409 synchronized (CustomConfigurator.class) { 410 processXML(document); 411 } 412 } catch (SAXException | IOException | ParserConfigurationException ex) { 413 PreferencesUtils.log(ex, "Error reading custom preferences:"); 414 } 415 PreferencesUtils.log("-- Reading complete --"); 416 } 417 418 public XMLCommandProcessor(Preferences mainPrefs) { 419 try { 420 this.mainPrefs = mainPrefs; 421 PreferencesUtils.resetLog(); 422 engine = Utils.getJavaScriptEngine(); 423 if (engine == null) { 424 throw new ScriptException("Failed to retrieve JavaScript engine"); 425 } 426 engine.eval("API={}; API.pref={}; API.fragments={};"); 427 428 engine.eval("homeDir='"+normalizeDirName(Config.getDirs().getPreferencesDirectory(false).getAbsolutePath()) +"';"); 429 engine.eval("josmVersion="+Version.getInstance().getVersion()+';'); 430 String className = CustomConfigurator.class.getName(); 431 engine.eval("API.messageBox="+className+".messageBox"); 432 engine.eval("API.askText=function(text) { return String("+className+".askForText(text));}"); 433 engine.eval("API.askOption="+className+".askForOption"); 434 engine.eval("API.downloadFile="+className+".downloadFile"); 435 engine.eval("API.downloadAndUnpackFile="+className+".downloadAndUnpackFile"); 436 engine.eval("API.deleteFile="+className+".deleteFile"); 437 engine.eval("API.plugin ="+className+".pluginOperation"); 438 engine.eval("API.pluginInstall = function(names) { "+className+".pluginOperation(names,'','');}"); 439 engine.eval("API.pluginUninstall = function(names) { "+className+".pluginOperation('',names,'');}"); 440 engine.eval("API.pluginDelete = function(names) { "+className+".pluginOperation('','',names);}"); 441 } catch (ScriptException ex) { 442 PreferencesUtils.log("Error: initializing script engine: "+ex.getMessage()); 443 Logging.error(ex); 444 } 445 } 446 447 private void processXML(Document document) { 448 processXmlFragment(document.getDocumentElement()); 449 } 450 451 private void processXmlFragment(Element root) { 452 NodeList childNodes = root.getChildNodes(); 453 int nops = childNodes.getLength(); 454 for (int i = 0; i < nops; i++) { 455 Node item = childNodes.item(i); 456 if (item.getNodeType() != Node.ELEMENT_NODE) continue; 457 String elementName = item.getNodeName(); 458 Element elem = (Element) item; 459 460 switch(elementName) { 461 case "var": 462 setVar(elem.getAttribute("name"), evalVars(elem.getAttribute("value"))); 463 break; 464 case "task": 465 tasksMap.put(elem.getAttribute("name"), elem); 466 break; 467 case "runtask": 468 if (processRunTaskElement(elem)) return; 469 break; 470 case "ask": 471 processAskElement(elem); 472 break; 473 case "if": 474 processIfElement(elem); 475 break; 476 case "else": 477 processElseElement(elem); 478 break; 479 case "break": 480 return; 481 case "plugin": 482 processPluginInstallElement(elem); 483 break; 484 case "messagebox": 485 processMsgBoxElement(elem); 486 break; 487 case "preferences": 488 processPreferencesElement(elem); 489 break; 490 case "download": 491 processDownloadElement(elem); 492 break; 493 case "delete": 494 processDeleteElement(elem); 495 break; 496 case "script": 497 processScriptElement(elem); 498 break; 499 default: 500 PreferencesUtils.log("Error: Unknown element " + elementName); 501 } 502 } 503 } 504 505 private void processPreferencesElement(Element item) { 506 String oper = evalVars(item.getAttribute("operation")); 507 String id = evalVars(item.getAttribute("id")); 508 509 if ("delete-keys".equals(oper)) { 510 String pattern = evalVars(item.getAttribute("pattern")); 511 String key = evalVars(item.getAttribute("key")); 512 PreferencesUtils.deletePreferenceKey(key, mainPrefs); 513 PreferencesUtils.deletePreferenceKeyByPattern(pattern, mainPrefs); 514 return; 515 } 516 517 Preferences tmpPref = readPreferencesFromDOMElement(item); 518 PreferencesUtils.showPrefs(tmpPref); 519 520 if (!id.isEmpty()) { 521 try { 522 String fragmentVar = "API.fragments['"+id+"']"; 523 engine.eval(fragmentVar+"={};"); 524 PreferencesUtils.loadPrefsToJS(engine, tmpPref, fragmentVar, false); 525 // we store this fragment as API.fragments['id'] 526 } catch (ScriptException ex) { 527 PreferencesUtils.log(ex, "Error: can not load preferences fragment:"); 528 } 529 } 530 531 if ("replace".equals(oper)) { 532 PreferencesUtils.log("Preferences replace: %d keys: %s\n", 533 tmpPref.getAllSettings().size(), tmpPref.getAllSettings().keySet().toString()); 534 PreferencesUtils.replacePreferences(tmpPref, mainPrefs); 535 } else if ("append".equals(oper)) { 536 PreferencesUtils.log("Preferences append: %d keys: %s\n", 537 tmpPref.getAllSettings().size(), tmpPref.getAllSettings().keySet().toString()); 538 PreferencesUtils.appendPreferences(tmpPref, mainPrefs); 539 } else if ("delete-values".equals(oper)) { 540 PreferencesUtils.deletePreferenceValues(tmpPref, mainPrefs); 541 } 542 } 543 544 private void processDeleteElement(Element item) { 545 String path = evalVars(item.getAttribute("path")); 546 String base = evalVars(item.getAttribute("base")); 547 deleteFile(path, base); 548 } 549 550 private void processDownloadElement(Element item) { 551 String base = evalVars(item.getAttribute("base")); 552 String dir = getDirectoryByAbbr(base); 553 if (dir == null) { 554 PreferencesUtils.log("Error: Can not find directory to place file, use base=cache, base=prefs or base=plugins attribute."); 555 return; 556 } 557 558 String path = evalVars(item.getAttribute("path")); 559 if (path.contains("..") || path.startsWith("/") || path.contains(":")) { 560 return; // some basic protection 561 } 562 563 String address = evalVars(item.getAttribute("url")); 564 if (address.isEmpty() || path.isEmpty()) { 565 PreferencesUtils.log("Error: Please specify url=\"where to get file\" and path=\"where to place it\""); 566 return; 567 } 568 569 String unzip = evalVars(item.getAttribute("unzip")); 570 String mkdir = evalVars(item.getAttribute("mkdir")); 571 processDownloadOperation(address, path, dir, "true".equals(mkdir), "true".equals(unzip)); 572 } 573 574 private static void processPluginInstallElement(Element elem) { 575 String install = elem.getAttribute("install"); 576 String uninstall = elem.getAttribute("remove"); 577 String delete = elem.getAttribute("delete"); 578 pluginOperation(install, uninstall, delete); 579 } 580 581 private void processMsgBoxElement(Element elem) { 582 String text = evalVars(elem.getAttribute("text")); 583 String locText = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".text")); 584 if (!locText.isEmpty()) text = locText; 585 586 String type = evalVars(elem.getAttribute("type")); 587 messageBox(type, text); 588 } 589 590 private void processAskElement(Element elem) { 591 String text = evalVars(elem.getAttribute("text")); 592 String locText = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".text")); 593 if (!locText.isEmpty()) text = locText; 594 String var = elem.getAttribute("var"); 595 if (var.isEmpty()) var = "result"; 596 597 String input = evalVars(elem.getAttribute("input")); 598 if ("true".equals(input)) { 599 setVar(var, askForText(text)); 600 } else { 601 String opts = evalVars(elem.getAttribute("options")); 602 String locOpts = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".options")); 603 if (!locOpts.isEmpty()) opts = locOpts; 604 setVar(var, String.valueOf(askForOption(text, opts))); 605 } 606 } 607 608 public void setVar(String name, String value) { 609 try { 610 engine.eval(name+"='"+value+"';"); 611 } catch (ScriptException ex) { 612 PreferencesUtils.log(ex, String.format("Error: Can not assign variable: %s=%s :", name, value)); 613 } 614 } 615 616 private void processIfElement(Element elem) { 617 String realValue = evalVars(elem.getAttribute("test")); 618 boolean v = false; 619 if ("true".equals(realValue) || "false".equals(realValue)) { 620 processXmlFragment(elem); 621 v = true; 622 } else { 623 PreferencesUtils.log("Error: Illegal test expression in if: %s=%s\n", elem.getAttribute("test"), realValue); 624 } 625 626 lastV = v; 627 } 628 629 private void processElseElement(Element elem) { 630 if (!lastV) { 631 processXmlFragment(elem); 632 } 633 } 634 635 private boolean processRunTaskElement(Element elem) { 636 String taskName = elem.getAttribute("name"); 637 Element task = tasksMap.get(taskName); 638 if (task != null) { 639 PreferencesUtils.log("EXECUTING TASK "+taskName); 640 processXmlFragment(task); // process task recursively 641 } else { 642 PreferencesUtils.log("Error: Can not execute task "+taskName); 643 return true; 644 } 645 return false; 646 } 647 648 private void processScriptElement(Element elem) { 649 String js = elem.getChildNodes().item(0).getTextContent(); 650 PreferencesUtils.log("Processing script..."); 651 try { 652 PreferencesUtils.modifyPreferencesByScript(engine, mainPrefs, js); 653 } catch (ScriptException ex) { 654 messageBox("e", ex.getMessage()); 655 PreferencesUtils.log(ex, "JS error:"); 656 } 657 PreferencesUtils.log("Script finished"); 658 } 659 660 /** 661 * substitute ${expression} = expression evaluated by JavaScript 662 * @param s string 663 * @return evaluation result 664 */ 665 private String evalVars(String s) { 666 Matcher mr = Pattern.compile("\\$\\{([^\\}]*)\\}").matcher(s); 667 StringBuffer sb = new StringBuffer(); 668 while (mr.find()) { 669 try { 670 String result = engine.eval(mr.group(1)).toString(); 671 mr.appendReplacement(sb, result); 672 } catch (ScriptException ex) { 673 PreferencesUtils.log(ex, String.format("Error: Can not evaluate expression %s :", mr.group(1))); 674 } 675 } 676 mr.appendTail(sb); 677 return sb.toString(); 678 } 679 680 private Preferences readPreferencesFromDOMElement(Element item) { 681 Preferences tmpPref = new Preferences(); 682 try { 683 Transformer xformer = XmlUtils.newSafeTransformerFactory().newTransformer(); 684 CharArrayWriter outputWriter = new CharArrayWriter(8192); 685 StreamResult out = new StreamResult(outputWriter); 686 687 xformer.transform(new DOMSource(item), out); 688 689 String fragmentWithReplacedVars = evalVars(outputWriter.toString()); 690 691 CharArrayReader reader = new CharArrayReader(fragmentWithReplacedVars.toCharArray()); 692 tmpPref.fromXML(reader); 693 } catch (TransformerException | XMLStreamException | IOException ex) { 694 PreferencesUtils.log(ex, "Error: can not read XML fragment:"); 695 } 696 697 return tmpPref; 698 } 699 700 private static String normalizeDirName(String dir) { 701 String s = dir.replace('\\', '/'); 702 if (s.endsWith("/")) s = s.substring(0, s.length()-1); 703 return s; 704 } 705 } 706}