001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS <http://nuxeo.com> and others 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Jean-Marc Orliaguet, Chalmers 011 * 012 * $Id$ 013 */ 014 015package org.nuxeo.theme.themes; 016 017import java.io.ByteArrayInputStream; 018import java.io.FileNotFoundException; 019import java.io.IOException; 020import java.io.InputStream; 021import java.net.MalformedURLException; 022import java.net.URL; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Date; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.LinkedHashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Properties; 032import java.util.Set; 033 034import javax.xml.parsers.DocumentBuilder; 035import javax.xml.parsers.DocumentBuilderFactory; 036import javax.xml.parsers.ParserConfigurationException; 037import javax.xml.xpath.XPath; 038import javax.xml.xpath.XPathConstants; 039import javax.xml.xpath.XPathExpressionException; 040import javax.xml.xpath.XPathFactory; 041 042import org.apache.commons.logging.Log; 043import org.apache.commons.logging.LogFactory; 044import org.nuxeo.runtime.api.Framework; 045import org.nuxeo.theme.Manager; 046import org.nuxeo.theme.elements.Element; 047import org.nuxeo.theme.elements.ElementFactory; 048import org.nuxeo.theme.elements.ElementFormatter; 049import org.nuxeo.theme.elements.ThemeElement; 050import org.nuxeo.theme.formats.Format; 051import org.nuxeo.theme.formats.FormatFactory; 052import org.nuxeo.theme.formats.styles.Style; 053import org.nuxeo.theme.fragments.Fragment; 054import org.nuxeo.theme.fragments.FragmentFactory; 055import org.nuxeo.theme.nodes.NodeException; 056import org.nuxeo.theme.perspectives.PerspectiveType; 057import org.nuxeo.theme.presets.CustomPresetType; 058import org.nuxeo.theme.presets.PresetManager; 059import org.nuxeo.theme.presets.PresetType; 060import org.nuxeo.theme.properties.FieldIO; 061import org.nuxeo.theme.resources.ResourceBank; 062import org.nuxeo.theme.types.TypeFamily; 063import org.nuxeo.theme.types.TypeRegistry; 064import org.w3c.dom.Document; 065import org.w3c.dom.NamedNodeMap; 066import org.w3c.dom.Node; 067import org.w3c.dom.NodeList; 068import org.xml.sax.InputSource; 069import org.xml.sax.SAXException; 070 071public class ThemeParser { 072 073 private static final Log log = LogFactory.getLog(ThemeParser.class); 074 075 private static final String DOCROOT_NAME = "theme"; 076 077 private static final XPath xpath = XPathFactory.newInstance().newXPath(); 078 079 public static void registerTheme(final ThemeDescriptor themeDescriptor, final boolean preload) 080 throws ThemeIOException { 081 registerTheme(themeDescriptor, null, preload); 082 } 083 084 public static void registerTheme(final ThemeDescriptor themeDescriptor, final String xmlSource, 085 final boolean preload) throws ThemeIOException { 086 final String src = themeDescriptor.getSrc(); 087 InputStream in = null; 088 try { 089 if (xmlSource == null) { 090 URL url = null; 091 try { 092 url = new URL(src); 093 } catch (MalformedURLException e) { 094 if (themeDescriptor.getContext() != null) { 095 url = themeDescriptor.getContext().getResource(src); 096 } else { 097 url = Thread.currentThread().getContextClassLoader().getResource(src); 098 } 099 } 100 if (url == null) { 101 throw new ThemeIOException("Incorrect theme URL: " + src); 102 } 103 in = url.openStream(); 104 } else { 105 in = new ByteArrayInputStream(xmlSource.getBytes()); 106 } 107 registerThemeFromInputStream(themeDescriptor, in, preload); 108 } catch (FileNotFoundException e) { 109 throw new ThemeIOException("File not found: " + src, e); 110 } catch (IOException e) { 111 throw new ThemeIOException("Could not open file: " + src, e); 112 } catch (ThemeException e) { 113 throw new ThemeIOException("Parsing error: " + src, e); 114 } finally { 115 if (in != null) { 116 try { 117 in.close(); 118 } catch (IOException e) { 119 log.error(e); 120 } finally { 121 in = null; 122 } 123 } 124 } 125 } 126 127 private static void registerThemeFromInputStream(final ThemeDescriptor themeDescriptor, final InputStream in, 128 boolean preload) throws ThemeIOException, ThemeException { 129 String themeName = null; 130 131 final InputSource is = new InputSource(in); 132 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 133 try { 134 dbf.setFeature("http://xml.org/sax/features/validation", false); 135 dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 136 } catch (ParserConfigurationException e) { 137 log.debug("Could not set DTD non-validation feature"); 138 } 139 140 DocumentBuilder db; 141 try { 142 db = dbf.newDocumentBuilder(); 143 } catch (ParserConfigurationException e) { 144 throw new ThemeIOException(e); 145 } 146 Document document; 147 try { 148 document = db.parse(is); 149 } catch (SAXException e) { 150 throw new ThemeIOException(e); 151 } catch (IOException e) { 152 throw new ThemeIOException(e); 153 } 154 final org.w3c.dom.Element docElem = document.getDocumentElement(); 155 if (!docElem.getNodeName().equals(DOCROOT_NAME)) { 156 throw new ThemeIOException("No <" + DOCROOT_NAME + "> document tag found in " + in.toString() 157 + ", ignoring the resource."); 158 } 159 160 themeName = docElem.getAttributes().getNamedItem("name").getNodeValue(); 161 if (!ThemeManager.validateThemeName(themeName)) { 162 throw new ThemeIOException( 163 "Theme names may only contain alpha-numeric characters, underscores and hyphens: " + themeName); 164 } 165 themeDescriptor.setName(themeName); 166 167 loadTheme(themeDescriptor, docElem, preload); 168 } 169 170 private static void loadTheme(ThemeDescriptor themeDescriptor, org.w3c.dom.Element docElem, boolean preload) 171 throws ThemeException, ThemeIOException { 172 final ThemeManager themeManager = Manager.getThemeManager(); 173 174 // remove old theme 175 String themeName = themeDescriptor.getName(); 176 ThemeElement oldTheme = themeManager.getThemeByName(themeName); 177 if (oldTheme != null) { 178 try { 179 themeManager.destroyElement(oldTheme); 180 } catch (NodeException e) { 181 throw new ThemeIOException("Failed to destroy theme: " + themeName, e); 182 } 183 } 184 185 Node baseNode = getBaseNode(docElem); 186 187 final Map<Integer, String> inheritanceMap = new HashMap<Integer, String>(); 188 final Map<Style, Map<String, Properties>> commonStyles = new LinkedHashMap<Style, Map<String, Properties>>(); 189 190 // create a new theme 191 ThemeElement theme = (ThemeElement) ElementFactory.create("theme"); 192 theme.setName(themeName); 193 Node description = docElem.getAttributes().getNamedItem("description"); 194 if (description != null) { 195 theme.setDescription(description.getNodeValue()); 196 } 197 198 String resourceBankName = null; 199 Node resourceBankNode = docElem.getAttributes().getNamedItem("resource-bank"); 200 if (resourceBankNode != null) { 201 resourceBankName = resourceBankNode.getNodeValue(); 202 themeDescriptor.setResourceBankName(resourceBankName); 203 } 204 205 Node templateEngines = docElem.getAttributes().getNamedItem("template-engines"); 206 if (templateEngines != null) { 207 themeDescriptor.setTemplateEngines(Arrays.asList(templateEngines.getNodeValue().split(","))); 208 } 209 210 if (preload) { 211 // Only register pages 212 registerThemePages(theme, baseNode); 213 214 } else { 215 // Register resources from remote bank 216 if (resourceBankName != null) { 217 try { 218 ResourceBank resourceBank = ThemeManager.getResourceBank(resourceBankName); 219 resourceBank.connect(themeName); 220 } catch (ThemeException e) { 221 log.warn("Resource bank not found: " + resourceBankName); 222 } 223 } 224 225 // register custom presets 226 for (Node n : getChildElementsByTagName(docElem, "presets")) { 227 parsePresets(theme, n); 228 } 229 230 // register formats 231 for (Node n : getChildElementsByTagName(docElem, "formats")) { 232 parseFormats(theme, docElem, commonStyles, inheritanceMap, n); 233 } 234 235 // setup style inheritance 236 for (Map.Entry<Integer, String> entry : inheritanceMap.entrySet()) { 237 Integer styleUid = entry.getKey(); 238 String inheritedStyleName = entry.getValue(); 239 Format style = ThemeManager.getFormatById(styleUid); 240 Format inheritedStyle = (Format) themeManager.getNamedObject(themeName, "style", inheritedStyleName); 241 if (inheritedStyle == null) { 242 log.warn("Cannot make style inherit from unknown style : " + inheritedStyleName); 243 continue; 244 } 245 themeManager.makeFormatInherit(style, inheritedStyle); 246 } 247 248 // styles created by the parser 249 createCommonStyles(themeName, commonStyles); 250 251 // register element properties 252 for (Node n : getChildElementsByTagName(docElem, "properties")) { 253 parseProperties(docElem, n); 254 } 255 256 parseLayout(theme, baseNode); 257 258 themeManager.removeOrphanedFormats(); 259 } 260 261 if (preload) { 262 log.debug("Registered THEME: " + themeName); 263 themeDescriptor.setLastLoaded(null); 264 } else { 265 log.debug("Loaded THEME: " + themeName); 266 themeDescriptor.setLastLoaded(new Date()); 267 } 268 269 // Register in the type registry 270 themeManager.registerTheme(theme); 271 } 272 273 public static boolean checkElementName(String name) throws ThemeIOException { 274 return name.matches("[a-z][a-z0-9_\\-\\s]+"); 275 } 276 277 public static void registerThemePages(final Element parent, Node node) throws ThemeIOException, ThemeException { 278 for (Node n : getChildElements(node)) { 279 String nodeName = n.getNodeName(); 280 NamedNodeMap attributes = n.getAttributes(); 281 Element elem; 282 if ("page".equals(nodeName)) { 283 elem = ElementFactory.create(nodeName); 284 285 Node nameAttr = attributes.getNamedItem("name"); 286 if (nameAttr != null) { 287 String elementName = nameAttr.getNodeValue(); 288 if (checkElementName(elementName)) { 289 elem.setName(elementName); 290 } else { 291 throw new ThemeIOException("Page name not allowed: " + elementName); 292 } 293 } 294 295 try { 296 parent.addChild(elem); 297 } catch (NodeException e) { 298 throw new ThemeIOException("Failed to parse layout.", e); 299 } 300 } 301 } 302 } 303 304 public static void parseLayout(final Element parent, Node node) throws ThemeIOException, ThemeException { 305 TypeRegistry typeRegistry = Manager.getTypeRegistry(); 306 ThemeManager themeManager = Manager.getThemeManager(); 307 for (String formatName : typeRegistry.getTypeNames(TypeFamily.FORMAT)) { 308 Format format = (Format) node.getUserData(formatName); 309 if (format != null) { 310 if (ElementFormatter.getElementsFor(format).isEmpty()) { 311 ElementFormatter.setFormat(parent, format); 312 } else { 313 Format duplicatedFormat = themeManager.duplicateFormat(format); 314 ElementFormatter.setFormat(parent, duplicatedFormat); 315 } 316 } 317 } 318 319 Properties properties = (Properties) node.getUserData("properties"); 320 if (properties != null) { 321 FieldIO.updateFieldsFromProperties(parent, properties); 322 } 323 324 for (Node n : getChildElements(node)) { 325 String nodeName = n.getNodeName(); 326 NamedNodeMap attributes = n.getAttributes(); 327 Element elem; 328 329 if ("fragment".equals(nodeName)) { 330 String fragmentType = attributes.getNamedItem("type").getNodeValue(); 331 elem = FragmentFactory.create(fragmentType); 332 if (elem == null) { 333 log.error("Could not create fragment: " + fragmentType); 334 continue; 335 } 336 Fragment fragment = (Fragment) elem; 337 Node perspectives = attributes.getNamedItem("perspectives"); 338 if (perspectives != null) { 339 for (String perspectiveName : perspectives.getNodeValue().split(",")) { 340 341 PerspectiveType perspective = (PerspectiveType) typeRegistry.lookup(TypeFamily.PERSPECTIVE, 342 perspectiveName); 343 344 if (perspective == null) { 345 log.warn("Could not find perspective: " + perspectiveName); 346 } else { 347 fragment.setVisibleInPerspective(perspective); 348 } 349 } 350 } 351 } else { 352 elem = ElementFactory.create(nodeName); 353 } 354 355 if (elem == null) { 356 throw new ThemeIOException("Could not parse node: " + nodeName); 357 } 358 359 Node nameAttr = attributes.getNamedItem("name"); 360 if (nameAttr != null) { 361 String elementName = nameAttr.getNodeValue(); 362 if (checkElementName(elementName)) { 363 elem.setName(elementName); 364 } else { 365 log.warn("Element names may only contain lower-case alpha-numeric characters, digits, underscores, spaces and dashes: " 366 + elementName); 367 } 368 } 369 370 Node classAttr = attributes.getNamedItem("class"); 371 if (classAttr != null) { 372 elem.setCssClassName(classAttr.getNodeValue()); 373 } 374 375 String description = getCommentAssociatedTo(n); 376 if (description != null) { 377 elem.setDescription(description); 378 } 379 380 try { 381 parent.addChild(elem); 382 } catch (NodeException e) { 383 throw new ThemeIOException("Failed to parse layout.", e); 384 } 385 parseLayout(elem, n); 386 } 387 } 388 389 public static void parsePresets(final ThemeElement theme, Node node) { 390 final TypeRegistry typeRegistry = Manager.getTypeRegistry(); 391 final String themeName = theme.getName(); 392 PresetManager.clearCustomPresets(themeName); 393 for (Node n : getChildElements(node)) { 394 NamedNodeMap attrs = n.getAttributes(); 395 final String name = attrs.getNamedItem("name").getNodeValue(); 396 final String category = attrs.getNamedItem("category").getNodeValue(); 397 final String value = n.getTextContent(); 398 final String group = themeName; // use the theme's name as 399 // group name 400 401 final Node labelAttr = attrs.getNamedItem("label"); 402 String label = ""; 403 if (labelAttr != null) { 404 label = labelAttr.getNodeValue(); 405 } 406 407 final Node descriptionAttr = attrs.getNamedItem("description"); 408 String description = ""; 409 if (descriptionAttr != null) { 410 description = descriptionAttr.getNodeValue(); 411 } 412 413 PresetType preset = new CustomPresetType(name, value, group, category, label, description); 414 typeRegistry.register(preset); 415 } 416 } 417 418 public static void parseFormats(final ThemeElement theme, org.w3c.dom.Element doc, 419 Map<Style, Map<String, Properties>> commonStyles, Map<Integer, String> inheritanceMap, Node node) 420 throws ThemeIOException, ThemeException { 421 Node baseNode = getBaseNode(doc); 422 String themeName = theme.getName(); 423 424 String resourceBankName = null; 425 ThemeDescriptor themeDescriptor = ThemeManager.getThemeDescriptorByThemeName(themeName); 426 if (themeDescriptor != null) { 427 resourceBankName = themeDescriptor.getResourceBankName(); 428 } 429 430 ThemeManager themeManager = Manager.getThemeManager(); 431 432 for (Node n : getChildElements(node)) { 433 String nodeName = n.getNodeName(); 434 NamedNodeMap attributes = n.getAttributes(); 435 Node elementItem = attributes.getNamedItem("element"); 436 String elementXPath = null; 437 if (elementItem != null) { 438 elementXPath = elementItem.getNodeValue(); 439 } 440 441 Format format; 442 try { 443 format = FormatFactory.create(nodeName); 444 } catch (ThemeException e) { 445 throw new ThemeIOException(e); 446 } 447 format.setProperties(getPropertiesFromNode(n)); 448 449 String description = getCommentAssociatedTo(n); 450 if (description != null) { 451 format.setDescription(description); 452 } 453 454 if ("widget".equals(nodeName)) { 455 List<Node> viewNodes = getChildElementsByTagName(n, "view"); 456 if (!viewNodes.isEmpty()) { 457 format.setName(viewNodes.get(0).getTextContent()); 458 } 459 460 } else if ("layout".equals(nodeName)) { 461 // TODO: validate layout properties 462 463 } else if ("style".equals(nodeName)) { 464 Node nameAttr = attributes.getNamedItem("name"); 465 Style style = (Style) format; 466 467 // register the style name 468 String styleName = null; 469 if (nameAttr != null) { 470 styleName = nameAttr.getNodeValue(); 471 // the style may have been registered already 472 Style registeredStyle = (Style) themeManager.getNamedObject(themeName, "style", styleName); 473 if (registeredStyle == null) { 474 style.setName(styleName); 475 themeManager.setNamedObject(themeName, "style", style); 476 } else { 477 style = registeredStyle; 478 } 479 } 480 481 Node inheritedAttr = attributes.getNamedItem("inherit"); 482 if (inheritedAttr != null) { 483 String inheritedName = inheritedAttr.getNodeValue(); 484 if ("".equals(inheritedName)) { 485 continue; 486 } 487 inheritanceMap.put(style.getUid(), inheritedName); 488 } 489 490 Node remoteAttr = attributes.getNamedItem("remote"); 491 if (remoteAttr != null) { 492 Boolean remote = Boolean.valueOf(remoteAttr.getNodeValue()); 493 if (style.isNamed()) { 494 style.setRemote(remote); 495 } else { 496 log.warn("Only named styles can be remote, ignoring remote attribute on" + style.getUid()); 497 } 498 } 499 500 if (styleName != null && elementXPath != null) { 501 log.warn("Style parser: named style '" + styleName + "' cannot have an 'element' attribute: '" 502 + elementXPath + "'."); 503 continue; 504 } 505 506 List<Node> selectorNodes = getChildElementsByTagName(n, "selector"); 507 508 if (style.isRemote() && resourceBankName != null) { 509 if (!selectorNodes.isEmpty()) { 510 style.setCustomized(true); 511 } 512 } 513 514 // Use style properties from the theme 515 for (Node selectorNode : selectorNodes) { 516 NamedNodeMap attrs = selectorNode.getAttributes(); 517 Node pathAttr = attrs.getNamedItem("path"); 518 if (pathAttr == null) { 519 log.warn(String.format("Style parser: named style '%s' has a selector with no path: ignored", 520 styleName)); 521 continue; 522 } 523 String path = pathAttr.getNodeValue(); 524 525 String viewName = null; 526 Node viewAttr = attrs.getNamedItem("view"); 527 if (viewAttr != null) { 528 viewName = viewAttr.getNodeValue(); 529 } 530 531 String selectorDescription = getCommentAssociatedTo(selectorNode); 532 if (selectorDescription != null) { 533 style.setSelectorDescription(path, viewName, selectorDescription); 534 } 535 536 // BBB: remove in a later release 537 if (elementXPath != null && (viewName == null || viewName.equals("*"))) { 538 log.warn("Style parser: trying to guess the view name for: " + elementXPath); 539 viewName = guessViewNameFor(doc, elementXPath); 540 if (viewName == null) { 541 if (!commonStyles.containsKey(style)) { 542 commonStyles.put(style, new LinkedHashMap<String, Properties>()); 543 } 544 commonStyles.get(style).put(path, getPropertiesFromNode(selectorNode)); 545 } 546 } 547 548 if (styleName != null) { 549 if (viewName != null) { 550 log.info("Style parser: ignoring view name '" + viewName + "' in named style '" + styleName 551 + "'."); 552 } 553 viewName = "*"; 554 } 555 556 if (viewName != null) { 557 style.setPropertiesFor(viewName, path, getPropertiesFromNode(selectorNode)); 558 } 559 } 560 } 561 562 themeManager.registerFormat(format); 563 if (elementXPath != null) { 564 if ("".equals(elementXPath)) { 565 baseNode.setUserData(nodeName, format, null); 566 } else { 567 for (Node element : getNodesByXPath(baseNode, elementXPath)) { 568 element.setUserData(nodeName, format, null); 569 } 570 } 571 } 572 } 573 574 } 575 576 public static void createCommonStyles(String themeName, Map<Style, Map<String, Properties>> commonStyles) 577 throws ThemeException { 578 ThemeManager themeManager = Manager.getThemeManager(); 579 int count = 1; 580 for (Style parent : commonStyles.keySet()) { 581 Style s = (Style) FormatFactory.create("style"); 582 String name = ""; 583 while (true) { 584 name = String.format("common style %s", count); 585 if (themeManager.getNamedObject(themeName, "style", name) == null) { 586 break; 587 } 588 count += 1; 589 } 590 s.setName(name); 591 themeManager.registerFormat(s); 592 themeManager.setNamedObject(themeName, "style", s); 593 Map<String, Properties> map = commonStyles.get(parent); 594 for (Map.Entry<String, Properties> entry : map.entrySet()) { 595 s.setPropertiesFor("*", entry.getKey(), entry.getValue()); 596 } 597 // if the style already inherits, preserve the inheritance 598 Style ancestor = (Style) ThemeManager.getAncestorFormatOf(parent); 599 if (ancestor != null) { 600 themeManager.makeFormatInherit(s, ancestor); 601 } 602 603 themeManager.makeFormatInherit(parent, s); 604 log.info("Created extra style: " + s.getName()); 605 } 606 } 607 608 public static void parseProperties(org.w3c.dom.Element doc, Node node) throws ThemeIOException { 609 NamedNodeMap attributes = node.getAttributes(); 610 Node elementAttr = attributes.getNamedItem("element"); 611 if (elementAttr == null) { 612 throw new ThemeIOException("<properties> node has no 'element' attribute."); 613 } 614 String elementXPath = elementAttr.getNodeValue(); 615 616 Node baseNode = getBaseNode(doc); 617 Node element = null; 618 try { 619 element = (Node) xpath.evaluate(elementXPath, baseNode, XPathConstants.NODE); 620 } catch (XPathExpressionException e) { 621 throw new ThemeIOException(e); 622 } 623 if (element == null) { 624 throw new ThemeIOException("Could not find the element associated to: " + elementXPath); 625 } 626 Properties properties = getPropertiesFromNode(node); 627 if (properties != null) { 628 element.setUserData("properties", properties, null); 629 } 630 } 631 632 private static Properties getPropertiesFromNode(Node node) { 633 Properties properties = new Properties(); 634 for (Node n : getChildElements(node)) { 635 String textContent = n.getTextContent(); 636 Node presetAttr = n.getAttributes().getNamedItem("preset"); 637 if (presetAttr != null) { 638 String presetName = presetAttr.getNodeValue(); 639 if (presetName != null) { 640 textContent = String.format("\"%s\"", presetName); 641 } 642 } 643 properties.setProperty(n.getNodeName(), Framework.expandVars(textContent)); 644 } 645 return properties; 646 } 647 648 private static List<Node> getChildElements(Node node) { 649 List<Node> nodes = new ArrayList<Node>(); 650 NodeList childNodes = node.getChildNodes(); 651 for (int i = 0; i < childNodes.getLength(); i++) { 652 Node n = childNodes.item(i); 653 if (n.getNodeType() == Node.ELEMENT_NODE) { 654 nodes.add(n); 655 } 656 } 657 return nodes; 658 } 659 660 public static List<Node> getChildElementsByTagName(Node node, String tagName) { 661 List<Node> nodes = new ArrayList<Node>(); 662 NodeList childNodes = node.getChildNodes(); 663 for (int i = 0; i < childNodes.getLength(); i++) { 664 Node n = childNodes.item(i); 665 if (n.getNodeType() == Node.ELEMENT_NODE && tagName.equals(n.getNodeName())) { 666 nodes.add(n); 667 } 668 } 669 return nodes; 670 } 671 672 public static Node getBaseNode(org.w3c.dom.Element doc) throws ThemeIOException { 673 Node baseNode = null; 674 try { 675 baseNode = (Node) xpath.evaluate('/' + DOCROOT_NAME + "/layout", doc, XPathConstants.NODE); 676 } catch (XPathExpressionException e) { 677 throw new ThemeIOException(e); 678 } 679 if (baseNode == null) { 680 throw new ThemeIOException("No <layout> section found."); 681 } 682 return baseNode; 683 } 684 685 private static String getCommentAssociatedTo(Node node) { 686 Node n = node; 687 while (true) { 688 n = n.getPreviousSibling(); 689 if (n == null) { 690 break; 691 } 692 if (n.getNodeType() == Node.ELEMENT_NODE) { 693 break; 694 } 695 if (n.getNodeType() == Node.COMMENT_NODE) { 696 return n.getNodeValue().trim(); 697 } 698 } 699 return null; 700 } 701 702 // BBB shouldn't have to guess view names 703 private static String guessViewNameFor(org.w3c.dom.Element doc, String elementXPath) { 704 NodeList widgetNodes = doc.getElementsByTagName("widget"); 705 Set<String> candidates = new HashSet<String>(); 706 String[] elements = elementXPath.split("\\|"); 707 for (int i = 0; i < widgetNodes.getLength(); i++) { 708 Node node = widgetNodes.item(i); 709 NamedNodeMap attributes = node.getAttributes(); 710 Node elementAttr = attributes.getNamedItem("element"); 711 if (elementAttr != null) { 712 String[] widgetElements = elementAttr.getNodeValue().split("\\|"); 713 for (String element : elements) { 714 for (String widgetElement : widgetElements) { 715 if (element.equals(widgetElement)) { 716 List<Node> viewNodes = getChildElementsByTagName(node, "view"); 717 if (!viewNodes.isEmpty()) { 718 candidates.add(viewNodes.get(0).getTextContent()); 719 } 720 } 721 } 722 } 723 } 724 } 725 if (candidates.size() == 1) { 726 return candidates.iterator().next(); 727 } 728 return null; 729 } 730 731 private static List<Node> getNodesByXPath(Node baseNode, String elementXPath) throws ThemeIOException { 732 final List<Node> nodes = new ArrayList<Node>(); 733 if (elementXPath != null) { 734 try { 735 NodeList elementNodes = (NodeList) xpath.evaluate(elementXPath, baseNode, XPathConstants.NODESET); 736 for (int i = 0; i < elementNodes.getLength(); i++) { 737 nodes.add(elementNodes.item(i)); 738 } 739 } catch (XPathExpressionException e) { 740 throw new ThemeIOException(e); 741 } 742 } 743 return nodes; 744 } 745}