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