001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ade.configuration.formatters; 029 030import org.opencms.ade.configuration.CmsConfigurationReader; 031import org.opencms.ade.configuration.CmsPropertyConfig; 032import org.opencms.configuration.CmsConfigurationException; 033import org.opencms.file.CmsObject; 034import org.opencms.file.CmsResource; 035import org.opencms.file.types.CmsResourceTypeFunctionConfig; 036import org.opencms.file.types.I_CmsResourceType; 037import org.opencms.i18n.CmsLocaleManager; 038import org.opencms.jsp.util.CmsFunctionRenderer; 039import org.opencms.jsp.util.CmsMacroFormatterResolver; 040import org.opencms.main.CmsException; 041import org.opencms.main.CmsLog; 042import org.opencms.main.OpenCms; 043import org.opencms.relations.CmsLink; 044import org.opencms.util.CmsStringUtil; 045import org.opencms.util.CmsUUID; 046import org.opencms.xml.containerpage.CmsFlexFormatterBean; 047import org.opencms.xml.containerpage.CmsFormatterBean; 048import org.opencms.xml.containerpage.CmsFunctionFormatterBean; 049import org.opencms.xml.containerpage.CmsMacroFormatterBean; 050import org.opencms.xml.containerpage.CmsMetaMapping; 051import org.opencms.xml.containerpage.I_CmsFormatterBean; 052import org.opencms.xml.content.CmsXmlContent; 053import org.opencms.xml.content.CmsXmlContentProperty; 054import org.opencms.xml.content.CmsXmlContentRootLocation; 055import org.opencms.xml.content.I_CmsXmlContentLocation; 056import org.opencms.xml.content.I_CmsXmlContentValueLocation; 057import org.opencms.xml.types.CmsXmlVfsFileValue; 058import org.opencms.xml.types.I_CmsXmlContentValue; 059 060import java.util.ArrayList; 061import java.util.Collections; 062import java.util.HashMap; 063import java.util.HashSet; 064import java.util.LinkedHashMap; 065import java.util.List; 066import java.util.Locale; 067import java.util.Map; 068import java.util.Set; 069 070import org.apache.commons.logging.Log; 071 072import com.google.common.collect.ArrayListMultimap; 073import com.google.common.collect.Lists; 074 075/** 076 * Parses formatter beans from formatter configuration XML contents.<p> 077 */ 078public class CmsFormatterBeanParser { 079 080 /** 081 * Exception for the errors in the configuration file not covered by other exception types.<p> 082 */ 083 public static class ParseException extends Exception { 084 085 /** Serial version id. */ 086 private static final long serialVersionUID = 1L; 087 088 /** 089 * Creates a new exception.<p> 090 * 091 * @param message the error message 092 */ 093 public ParseException(String message) { 094 095 super(message); 096 } 097 098 /** 099 * Creates a new exception.<p> 100 * 101 * @param message the error message 102 * @param cause the cause 103 */ 104 public ParseException(String message, Throwable cause) { 105 106 super(message, cause); 107 } 108 } 109 110 /** Content value node name. */ 111 public static final String N_ALLOWS_SETTINGS_IN_EDITOR = "AllowsSettingsInEditor"; 112 113 /** Content value node name. */ 114 public static final String N_ATTRIBUTE = "Attribute"; 115 116 /** Content value node name. */ 117 public static final String N_AUTO_ENABLED = "AutoEnabled"; 118 119 /** Content value node name. */ 120 public static final String N_CHOICE_NEW_LINK = "ChoiceNewLink"; 121 122 /** Content value node name. */ 123 public static final String N_CONTAINER_TYPE = "ContainerType"; 124 125 /** Content value node name. */ 126 public static final String N_CSS_INLINE = "CssInline"; 127 128 /** Content value node name. */ 129 public static final String N_CSS_LINK = "CssLink"; 130 131 /** Content value node name. */ 132 public static final String N_DEFAULT = "Default"; 133 134 /** Content value node name. */ 135 public static final String N_DEFAULT_CONTENT = "DefaultContent"; 136 137 /** Content value node name. */ 138 public static final String N_DESCRIPTION = "Description"; 139 140 /** Content value node name. */ 141 public static final String N_DETAIL = "Detail"; 142 143 /** Content value node name. */ 144 public static final String N_DISPLAY = "Display"; 145 146 /** Content value node name. */ 147 public static final String N_ELEMENT = "Element"; 148 149 /** Node name. */ 150 public static final String N_FORMATTER = "Formatter"; 151 152 /** Node name. */ 153 public static final String N_FORMATTERS = "Formatters"; 154 155 /** Content value node name. */ 156 public static final String N_HEAD_INCLUDE_CSS = "HeadIncludeCss"; 157 158 /** Content value node name. */ 159 public static final String N_HEAD_INCLUDE_JS = "HeadIncludeJs"; 160 161 /** Content value node name. */ 162 public static final String N_INCLUDE_SETTINGS = "IncludeSettings"; 163 164 /** Content value node name. */ 165 public static final String N_JAVASCRIPT_INLINE = "JavascriptInline"; 166 167 /** Content value node name. */ 168 public static final String N_JAVASCRIPT_LINK = "JavascriptLink"; 169 170 /** Content value node name. */ 171 public static final String N_JSP = "Jsp"; 172 173 /** Content value node name. */ 174 public static final String N_KEY = "Key"; 175 176 /** Node name. */ 177 public static final String N_MACRO = "Macro"; 178 179 /** Node name. */ 180 public static final String N_MACRO_NAME = "MacroName"; 181 182 /** Content value node name. */ 183 public static final String N_MATCH = "Match"; 184 185 /** Content value node name. */ 186 public static final String N_MAX_WIDTH = "MaxWidth"; 187 188 /** Content value node name. */ 189 public static final String N_META_MAPPING = "MetaMapping"; 190 191 /** Content value node name. */ 192 public static final String N_NESTED_FORMATTER_SETTINGS = "NestedFormatterSettings"; 193 194 /** Content value node name. */ 195 public static final String N_NICE_NAME = "NiceName"; 196 197 /** Content value node name. */ 198 public static final String N_ORDER = "Order"; 199 200 /** Content value node name. */ 201 public static final String N_PARAMETER = "Parameter"; 202 203 /** Content value node name. */ 204 public static final String N_PLACEHOLDER_MACRO = "PlaceholderMacro"; 205 206 /** Node name. */ 207 public static final String N_PLACEHOLDER_STRING_TEMPLATE = "PlaceholderStringTemplate"; 208 209 /** Content value node name. */ 210 public static final String N_PREVIEW = "Preview"; 211 212 /** Content value node name. */ 213 public static final String N_RANK = "Rank"; 214 215 /** Content value node name. */ 216 public static final String N_SEARCH_CONTENT = "SearchContent"; 217 218 /** Content value node name. */ 219 public static final String N_SETTING = "Setting"; 220 221 /** Content value node name. */ 222 public static final String N_STRICT_CONTAINERS = "StrictContainers"; 223 224 /** Node name. */ 225 public static final String N_STRING_TEMPLATE = "StringTemplate"; 226 227 /** Content value node name. */ 228 public static final String N_TYPE = "Type"; 229 230 /** Content value node name. */ 231 public static final String N_TYPES = "Types"; 232 233 /** Node name for the 'use meta mappings for normal elements' check box. */ 234 public static final String N_USE_META_MAPPINGS_FOR_NORMAL_ELEMENTS = "AlwaysApplyMetaMappings"; 235 236 /** Content value node name. */ 237 public static final String N_VALUE = "Value"; 238 239 /** Content value node name. */ 240 public static final String N_WIDTH = "Width"; 241 242 /** The key for the setting display type. */ 243 public static final String SETTING_DISPLAY_TYPE = "displayType"; 244 245 /** The logger instance for this class. */ 246 private static final Log LOG = CmsLog.getLog(CmsFormatterBeanParser.class); 247 248 /** Parsed field. */ 249 int m_width; 250 251 /** Additional setting configurations for includes. */ 252 private Map<CmsUUID, List<CmsXmlContentProperty>> m_additionalSettingConfigs = new HashMap<>(); 253 254 /** Parsed field. */ 255 private boolean m_autoEnabled; 256 257 /** The CMS object used for parsing. */ 258 private CmsObject m_cms; 259 260 /** Parsed field. */ 261 private Set<String> m_containerTypes; 262 263 /** Parsed field. */ 264 private List<String> m_cssPaths = new ArrayList<String>(); 265 266 /** Parsed field. */ 267 private boolean m_extractContent; 268 269 /** Parsed field. */ 270 private CmsResource m_formatterResource; 271 272 /** Parsed field. */ 273 private StringBuffer m_inlineCss = new StringBuffer(); 274 275 /** Parsed field. */ 276 private StringBuffer m_inlineJs = new StringBuffer(); 277 278 /** Parsed field. */ 279 private List<String> m_jsPaths = new ArrayList<String>(); 280 281 /** Parsed field. */ 282 private int m_maxWidth; 283 284 /** Parsed field. */ 285 private String m_niceName; 286 287 /** Parsed field. */ 288 private boolean m_preview; 289 290 /** Parsed field. */ 291 private int m_rank; 292 293 /** Parsed field. */ 294 private Set<String> m_resourceType; 295 296 /** Setting configurations read from content. **/ 297 private List<CmsXmlContentProperty> m_settingList = new ArrayList<>(); 298 299 /** Settings merged with included settings. */ 300 private Map<String, CmsXmlContentProperty> m_settings = new HashMap<>(); 301 302 /** 303 * Creates a new parser instance.<p> 304 * 305 * A new parser instance should be created for every formatter configuration you want to parse.<p> 306 * 307 * @param cms the CMS context to use for parsing 308 * @param settingConfigs the additional setting configurations used for includes 309 */ 310 public CmsFormatterBeanParser(CmsObject cms, Map<CmsUUID, List<CmsXmlContentProperty>> settingConfigs) { 311 312 m_cms = cms; 313 m_additionalSettingConfigs = settingConfigs; 314 } 315 316 /** 317 * Creates an xpath from the given components.<p> 318 * 319 * @param components the xpath componentns 320 * 321 * @return the composed xpath 322 */ 323 public static String path(String... components) { 324 325 return CmsStringUtil.joinPaths(components); 326 } 327 328 /** 329 * Reads the formatter bean from the given XML content.<p> 330 * 331 * @param content the formatter configuration XML content 332 * @param location a string indicating the location of the configuration 333 * @param id the id to use as the formatter id 334 * 335 * @return the parsed formatter bean 336 * 337 * @throws ParseException if parsing goes wrong 338 * @throws CmsException if something else goes wrong 339 */ 340 public I_CmsFormatterBean parse(CmsXmlContent content, String location, String id) 341 throws CmsException, ParseException { 342 343 String path = content.getFile().getRootPath(); 344 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(content.getFile()); 345 boolean isMacroFromatter = CmsFormatterConfigurationCache.TYPE_MACRO_FORMATTER.equals(type.getTypeName()); 346 boolean isFlexFormatter = CmsFormatterConfigurationCache.TYPE_FLEX_FORMATTER.equals(type.getTypeName()); 347 boolean isFunction = OpenCms.getResourceManager().matchResourceType( 348 CmsResourceTypeFunctionConfig.TYPE_NAME, 349 content.getFile().getTypeId()); 350 351 Locale en = Locale.ENGLISH; 352 I_CmsXmlContentValue niceName = content.getValue(N_NICE_NAME, en); 353 m_niceName = niceName != null ? niceName.getStringValue(m_cms) : null; 354 CmsXmlContentRootLocation root = new CmsXmlContentRootLocation(content, en); 355 I_CmsXmlContentValueLocation rankLoc = root.getSubValue(N_RANK); 356 if (rankLoc != null) { 357 String rankStr = rankLoc.getValue().getStringValue(m_cms); 358 if (rankStr != null) { 359 rankStr = rankStr.trim(); 360 } 361 int rank; 362 try { 363 rank = Integer.parseInt(rankStr); 364 } catch (NumberFormatException e) { 365 rank = CmsFormatterBean.DEFAULT_CONFIGURATION_RANK; 366 LOG.debug("Error parsing formatter rank.", e); 367 } 368 m_rank = rank; 369 } 370 371 m_resourceType = getStringSet(root, N_TYPE); 372 parseSettings(root); 373 List<I_CmsXmlContentValue> settingIncludes = content.getValues(N_INCLUDE_SETTINGS, en); 374 settingIncludes = Lists.reverse(settingIncludes); // make defaults from earlier include files 'win' when merging them into a map 375 Map<String, CmsXmlContentProperty> includesByIncludeName = new HashMap<>(); 376 for (I_CmsXmlContentValue settingInclude : settingIncludes) { 377 try { 378 CmsXmlVfsFileValue includeFileVal = (CmsXmlVfsFileValue)settingInclude; 379 CmsUUID includeSettingsId = includeFileVal.getLink(m_cms).getStructureId(); 380 List<CmsXmlContentProperty> includedSettings = m_additionalSettingConfigs.get(includeSettingsId); 381 if (includedSettings == null) { 382 continue; 383 } 384 for (CmsXmlContentProperty prop : includedSettings) { 385 String includeName = prop.getIncludeName(prop.getName()); 386 if (includeName != null) { 387 CmsXmlContentProperty existingProp = includesByIncludeName.get(includeName); 388 if (existingProp != null) { 389 LOG.warn("Conflict with included setting configuration: " + path); 390 } 391 includesByIncludeName.put(includeName, prop); 392 } 393 } 394 } catch (Exception e) { 395 LOG.error(e.getLocalizedMessage(), e); 396 } 397 } 398 399 Map<String, CmsXmlContentProperty> mergedSettings = new LinkedHashMap<>(); 400 401 for (CmsXmlContentProperty setting : m_settingList) { 402 String includeName = setting.getIncludeName(setting.getName()); 403 if (includeName == null) { 404 LOG.warn("Neither name nor include name given in setting definition in " + path); 405 continue; 406 } 407 CmsXmlContentProperty defaultSetting = includesByIncludeName.get(includeName); 408 CmsXmlContentProperty mergedSetting; 409 if (defaultSetting != null) { 410 mergedSetting = setting.mergeDefaults(defaultSetting); 411 } else { 412 mergedSetting = setting; 413 } 414 if (mergedSetting.getName() == null) { 415 LOG.warn("Invalid setting without name in " + path); 416 continue; 417 } 418 mergedSettings.put(mergedSetting.getName(), mergedSetting); 419 } 420 m_settings = mergedSettings; 421 422 String isDetailStr = getString(root, N_DETAIL, "false"); 423 boolean isDetail = Boolean.parseBoolean(isDetailStr); 424 425 String displayType = getString(root, N_DISPLAY, null); 426 if (CmsStringUtil.isEmptyOrWhitespaceOnly(displayType) || "false".equals(displayType)) { 427 displayType = null; 428 } else if (!m_settings.containsKey(SETTING_DISPLAY_TYPE)) { 429 m_settings.put( 430 SETTING_DISPLAY_TYPE, 431 new CmsXmlContentProperty( 432 SETTING_DISPLAY_TYPE, 433 "string", 434 "hidden", 435 null, 436 null, 437 null, 438 displayType, 439 null, 440 null, 441 null, 442 null)); 443 } 444 445 String isAllowSettingsStr = getString(root, N_ALLOWS_SETTINGS_IN_EDITOR, "false"); 446 boolean isAllowSettings = Boolean.parseBoolean(isAllowSettingsStr); 447 448 String isStrictContainersStr = getString(root, N_STRICT_CONTAINERS, "false"); 449 boolean isStrictContainers = Boolean.parseBoolean(isStrictContainersStr); 450 451 String description = getString(root, N_DESCRIPTION, null); 452 453 String autoEnabled = getString(root, N_AUTO_ENABLED, "false"); 454 m_autoEnabled = Boolean.parseBoolean(autoEnabled); 455 456 String nestedFormatterSettings = getString(root, N_NESTED_FORMATTER_SETTINGS, "false"); 457 boolean nestedFormatters = Boolean.parseBoolean(nestedFormatterSettings); 458 459 String useMetaMappinsForNormalElementsStr = getString(root, N_USE_META_MAPPINGS_FOR_NORMAL_ELEMENTS, "false"); 460 boolean useMetaMappingsForNormalElements = Boolean.parseBoolean(useMetaMappinsForNormalElementsStr); 461 462 // Functions which just have been created don't have any matching rules, but should fit anywhere 463 boolean strictMode = !isFunction; 464 parseMatch(root, strictMode); 465 466 List<CmsMetaMapping> mappings = parseMetaMappings(root); 467 Map<String, String> attributes = parseAttributes(root); 468 469 I_CmsFormatterBean formatterBean; 470 if (isMacroFromatter || isFlexFormatter) { 471 // setting macro formatter defaults 472 m_formatterResource = content.getFile(); 473 m_preview = false; 474 m_extractContent = true; 475 CmsResource defContentRes = null; 476 I_CmsXmlContentValueLocation defContentLoc = root.getSubValue(N_DEFAULT_CONTENT); 477 if (defContentLoc != null) { 478 CmsXmlVfsFileValue defContentValue = (CmsXmlVfsFileValue)(defContentLoc.getValue()); 479 CmsLink defContentLink = defContentValue.getLink(m_cms); 480 if (defContentLink != null) { 481 CmsUUID defContentID = defContentLink.getStructureId(); 482 defContentRes = m_cms.readResource(defContentID); 483 } 484 } 485 if (isMacroFromatter) { 486 String macroInput = getString(root, N_MACRO, ""); 487 String placeholderMacroInput = getString(root, N_PLACEHOLDER_MACRO, ""); 488 Map<String, CmsUUID> referencedFormatters = readReferencedFormatters(content); 489 formatterBean = new CmsMacroFormatterBean( 490 m_containerTypes, 491 m_formatterResource.getRootPath(), 492 m_formatterResource.getStructureId(), 493 m_width, 494 m_maxWidth, 495 m_extractContent, 496 location, 497 m_niceName, 498 description, 499 m_resourceType, 500 m_rank, 501 id, 502 defContentRes != null ? defContentRes.getRootPath() : null, 503 defContentRes != null ? defContentRes.getStructureId() : null, 504 m_settings, 505 m_autoEnabled, 506 isDetail, 507 displayType, 508 isAllowSettings, 509 macroInput, 510 placeholderMacroInput, 511 referencedFormatters, 512 m_cms.getRequestContext().getCurrentProject().isOnlineProject(), 513 mappings, 514 useMetaMappingsForNormalElements); 515 } else { 516 String stringTemplate = getString(root, N_STRING_TEMPLATE, ""); 517 String placeholder = getString(root, N_PLACEHOLDER_STRING_TEMPLATE, ""); 518 formatterBean = new CmsFlexFormatterBean( 519 m_containerTypes, 520 m_formatterResource.getRootPath(), 521 m_formatterResource.getStructureId(), 522 m_width, 523 m_maxWidth, 524 m_extractContent, 525 location, 526 m_niceName, 527 description, 528 m_resourceType, 529 m_rank, 530 id, 531 defContentRes != null ? defContentRes.getRootPath() : null, 532 defContentRes != null ? defContentRes.getStructureId() : null, 533 m_settings, 534 m_autoEnabled, 535 isDetail, 536 displayType, 537 isAllowSettings, 538 stringTemplate, 539 placeholder, 540 mappings, 541 useMetaMappingsForNormalElements); 542 } 543 } else { 544 I_CmsXmlContentValueLocation jspLoc = root.getSubValue(N_JSP); 545 CmsXmlVfsFileValue jspValue = (CmsXmlVfsFileValue)(jspLoc.getValue()); 546 CmsLink link = jspValue.getLink(m_cms); 547 548 CmsUUID jspID = null; 549 if (link == null) { 550 if (isFunction) { 551 CmsResource defaultFormatter = CmsFunctionRenderer.getDefaultFunctionJsp(m_cms); 552 jspID = defaultFormatter.getStructureId(); 553 } else { 554 // JSP link is not set (for example because the formatter configuration has just been created) 555 LOG.info("JSP link is null in formatter configuration: " + content.getFile().getRootPath()); 556 return null; 557 } 558 } else { 559 jspID = link.getStructureId(); 560 } 561 562 if (jspID == null) { 563 throw new CmsConfigurationException( 564 org.opencms.main.Messages.get().container( 565 org.opencms.main.Messages.ERR_READ_FORMATTER_CONFIG_4, 566 new Object[] { 567 link != null ? link.getUri() : " ??? ", 568 m_niceName, 569 location, 570 "" + m_resourceType})); 571 } 572 573 CmsResource formatterRes = m_cms.readResource(jspID); 574 m_formatterResource = formatterRes; 575 String previewStr = getString(root, N_PREVIEW, "false"); 576 m_preview = Boolean.parseBoolean(previewStr); 577 578 String searchableStr = getString(root, N_SEARCH_CONTENT, "true"); 579 m_extractContent = Boolean.parseBoolean(searchableStr); 580 parseHeadIncludes(root); 581 if (isFunction) { 582 CmsResource functionFormatter = m_cms.readResource(CmsResourceTypeFunctionConfig.FORMATTER_PATH); 583 Map<String, String[]> rparams = parseParams(root); 584 formatterBean = new CmsFunctionFormatterBean( 585 m_containerTypes, 586 m_formatterResource.getRootPath(), 587 m_formatterResource.getStructureId(), 588 functionFormatter.getStructureId(), 589 m_width, 590 m_maxWidth, 591 location, 592 m_cssPaths, 593 m_inlineCss.toString(), 594 m_jsPaths, 595 m_inlineJs.toString(), 596 m_niceName, 597 description, 598 id, 599 m_settings, 600 isAllowSettings, 601 isStrictContainers, 602 rparams); 603 } else { 604 formatterBean = new CmsFormatterBean( 605 m_containerTypes, 606 m_formatterResource.getRootPath(), 607 m_formatterResource.getStructureId(), 608 m_width, 609 m_maxWidth, 610 m_preview, 611 m_extractContent, 612 location, 613 m_cssPaths, 614 m_inlineCss.toString(), 615 m_jsPaths, 616 m_inlineJs.toString(), 617 m_niceName, 618 description, 619 m_resourceType, 620 m_rank, 621 id, 622 m_settings, 623 true, 624 m_autoEnabled, 625 isDetail, 626 displayType, 627 isAllowSettings, 628 isStrictContainers, 629 nestedFormatters, 630 mappings, 631 attributes, 632 useMetaMappingsForNormalElements); 633 } 634 } 635 636 return formatterBean; 637 } 638 639 /** 640 * Gets an XML string value.<p> 641 * 642 * @param val the location of the parent value 643 * @param path the path of the sub-value 644 * @param defaultValue the default value to use if no value was found 645 * 646 * @return the found value 647 */ 648 private String getString(I_CmsXmlContentLocation val, String path, String defaultValue) { 649 650 if ((val != null)) { 651 I_CmsXmlContentValueLocation subVal = val.getSubValue(path); 652 if ((subVal != null) && (subVal.getValue() != null)) { 653 return subVal.getValue().getStringValue(m_cms); 654 } 655 } 656 return defaultValue; 657 } 658 659 /** 660 * Returns a set of string values.<p> 661 * 662 * @param val the location of the parent value 663 * @param path the path of the sub-values 664 * 665 * @return a set of string values 666 */ 667 private Set<String> getStringSet(I_CmsXmlContentLocation val, String path) { 668 669 Set<String> valueSet = new HashSet<String>(); 670 if ((val != null)) { 671 List<I_CmsXmlContentValueLocation> singleValueLocs = val.getSubValues(path); 672 for (I_CmsXmlContentValueLocation singleValueLoc : singleValueLocs) { 673 String value = singleValueLoc.getValue().getStringValue(m_cms).trim(); 674 valueSet.add(value); 675 } 676 } 677 return valueSet; 678 } 679 680 /** 681 * Parses formatter attributes. 682 * 683 * @param formatterLoc the node location 684 * @return the map of formatter attributes (unmodifiable) 685 */ 686 private Map<String, String> parseAttributes(I_CmsXmlContentLocation formatterLoc) { 687 688 Map<String, String> result = new LinkedHashMap<>(); 689 for (I_CmsXmlContentValueLocation mappingLoc : formatterLoc.getSubValues(N_ATTRIBUTE)) { 690 String key = CmsConfigurationReader.getString(m_cms, mappingLoc.getSubValue(N_KEY)); 691 String value = CmsConfigurationReader.getString(m_cms, mappingLoc.getSubValue(N_VALUE)); 692 result.put(key, value); 693 } 694 return Collections.unmodifiableMap(result); 695 } 696 697 /** 698 * Parses the head includes.<p> 699 * 700 * @param formatterLoc the parent value location 701 */ 702 private void parseHeadIncludes(I_CmsXmlContentLocation formatterLoc) { 703 704 I_CmsXmlContentValueLocation headIncludeCss = formatterLoc.getSubValue(N_HEAD_INCLUDE_CSS); 705 if (headIncludeCss != null) { 706 for (I_CmsXmlContentValueLocation inlineCssLoc : headIncludeCss.getSubValues(N_CSS_INLINE)) { 707 String inlineCss = inlineCssLoc.getValue().getStringValue(m_cms); 708 m_inlineCss.append(inlineCss); 709 } 710 711 for (I_CmsXmlContentValueLocation cssLinkLoc : headIncludeCss.getSubValues(N_CSS_LINK)) { 712 CmsXmlVfsFileValue fileValue = (CmsXmlVfsFileValue)cssLinkLoc.getValue(); 713 CmsLink link = fileValue.getLink(m_cms); 714 if (link != null) { 715 String cssPath = link.getTarget(); 716 m_cssPaths.add(cssPath); 717 } 718 } 719 } 720 I_CmsXmlContentValueLocation headIncludeJs = formatterLoc.getSubValue(N_HEAD_INCLUDE_JS); 721 if (headIncludeJs != null) { 722 for (I_CmsXmlContentValueLocation inlineJsLoc : headIncludeJs.getSubValues(N_JAVASCRIPT_INLINE)) { 723 String inlineJs = inlineJsLoc.getValue().getStringValue(m_cms); 724 m_inlineJs.append(inlineJs); 725 } 726 for (I_CmsXmlContentValueLocation jsLinkLoc : headIncludeJs.getSubValues(N_JAVASCRIPT_LINK)) { 727 CmsXmlVfsFileValue fileValue = (CmsXmlVfsFileValue)jsLinkLoc.getValue(); 728 CmsLink link = fileValue.getLink(m_cms); 729 if (link != null) { 730 String jsPath = link.getTarget(); 731 m_jsPaths.add(jsPath); 732 } 733 } 734 } 735 } 736 737 /** 738 * Parses the matching criteria (container types or widths) for the formatter.<p> 739 * 740 * @param linkFormatterLoc the formatter value location 741 * @param strict if we should throw an error for incomplete match 742 * 743 * @throws ParseException if parsing goes wrong 744 */ 745 private void parseMatch(I_CmsXmlContentLocation linkFormatterLoc, boolean strict) throws ParseException { 746 747 Set<String> containerTypes = new HashSet<String>(); 748 I_CmsXmlContentValueLocation typesLoc = linkFormatterLoc.getSubValue(path(N_MATCH, N_TYPES)); 749 I_CmsXmlContentValueLocation widthLoc = linkFormatterLoc.getSubValue(path(N_MATCH, N_WIDTH)); 750 if (typesLoc != null) { 751 List<I_CmsXmlContentValueLocation> singleTypeLocs = typesLoc.getSubValues(N_CONTAINER_TYPE); 752 for (I_CmsXmlContentValueLocation singleTypeLoc : singleTypeLocs) { 753 String containerType = singleTypeLoc.getValue().getStringValue(m_cms).trim(); 754 containerTypes.add(containerType); 755 } 756 m_containerTypes = containerTypes; 757 } else if (widthLoc != null) { 758 String widthStr = getString(widthLoc, N_WIDTH, null); 759 String maxWidthStr = getString(widthLoc, N_MAX_WIDTH, null); 760 try { 761 m_width = Integer.parseInt(widthStr); 762 } catch (Exception e) { 763 throw new ParseException("Invalid container width: [" + widthStr + "]", e); 764 } 765 try { 766 m_maxWidth = Integer.parseInt(maxWidthStr); 767 } catch (Exception e) { 768 m_maxWidth = Integer.MAX_VALUE; 769 LOG.debug(maxWidthStr, e); 770 } 771 } else { 772 if (strict) { 773 throw new ParseException("Neither container types nor container widths defined!"); 774 } else { 775 m_width = -1; 776 m_maxWidth = Integer.MAX_VALUE; 777 } 778 } 779 } 780 781 /** 782 * Parses the mappings.<p> 783 * 784 * @param formatterLoc the formatter value location 785 * 786 * @return the mappings 787 */ 788 private List<CmsMetaMapping> parseMetaMappings(I_CmsXmlContentLocation formatterLoc) { 789 790 List<CmsMetaMapping> mappings = new ArrayList<CmsMetaMapping>(); 791 for (I_CmsXmlContentValueLocation mappingLoc : formatterLoc.getSubValues(N_META_MAPPING)) { 792 String key = CmsConfigurationReader.getString(m_cms, mappingLoc.getSubValue(N_KEY)); 793 String element = CmsConfigurationReader.getString(m_cms, mappingLoc.getSubValue(N_ELEMENT)); 794 String defaultValue = CmsConfigurationReader.getString(m_cms, mappingLoc.getSubValue(N_DEFAULT)); 795 String orderStr = CmsConfigurationReader.getString(m_cms, mappingLoc.getSubValue(N_ORDER)); 796 int order = 1000; 797 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(orderStr)) { 798 try { 799 order = Integer.parseInt(orderStr); 800 } catch (NumberFormatException e) { 801 // nothing to do 802 } 803 } 804 CmsMetaMapping mapping = new CmsMetaMapping(key, element, order, defaultValue); 805 mappings.add(mapping); 806 } 807 return mappings; 808 } 809 810 /** 811 * Parse parameters and put them in a map.<p> 812 * 813 * @param root the location from which to start parsing 814 * 815 * @return the parameter map 816 */ 817 private Map<String, String[]> parseParams(I_CmsXmlContentLocation root) { 818 819 // first use multimap for convenience, to group values for the same key, 820 // and then convert to result format 821 ArrayListMultimap<String, String> mmap = ArrayListMultimap.create(); 822 for (I_CmsXmlContentLocation location : root.getSubValues(N_PARAMETER)) { 823 String key = location.getSubValue(N_KEY).getValue().getStringValue(m_cms); 824 String value = location.getSubValue(N_VALUE).getValue().getStringValue(m_cms); 825 mmap.put(key, value); 826 } 827 Map<String, String[]> result = new HashMap<>(); 828 String[] emptyArray = new String[] {}; // need this for toArray 829 for (String key : mmap.keySet()) { 830 List<String> values = mmap.get(key); 831 String[] valuesArray = values.toArray(emptyArray); 832 result.put(key, valuesArray); 833 } 834 return result; 835 836 } 837 838 /** 839 * Parses the settings.<p> 840 * 841 * @param formatterLoc the formatter value location 842 */ 843 private void parseSettings(I_CmsXmlContentLocation formatterLoc) { 844 845 for (I_CmsXmlContentValueLocation settingLoc : formatterLoc.getSubValues(N_SETTING)) { 846 CmsPropertyConfig propConfig = CmsConfigurationReader.parseProperty(m_cms, settingLoc); 847 CmsXmlContentProperty property = propConfig.getPropertyData(); 848 m_settingList.add(property); 849 } 850 } 851 852 /** 853 * Reads the referenced formatters.<p> 854 * 855 * @param xmlContent the XML content 856 * 857 * @return the referenced formatters 858 */ 859 private Map<String, CmsUUID> readReferencedFormatters(CmsXmlContent xmlContent) { 860 861 Map<String, CmsUUID> result = new LinkedHashMap<String, CmsUUID>(); 862 List<I_CmsXmlContentValue> formatters = xmlContent.getValues( 863 CmsMacroFormatterResolver.N_FORMATTERS, 864 CmsLocaleManager.MASTER_LOCALE); 865 for (I_CmsXmlContentValue formatterValue : formatters) { 866 CmsXmlVfsFileValue file = (CmsXmlVfsFileValue)xmlContent.getValue( 867 formatterValue.getPath() + "/" + CmsMacroFormatterResolver.N_FORMATTER, 868 CmsLocaleManager.MASTER_LOCALE); 869 CmsUUID formatterId = file.getLink(m_cms).getStructureId(); 870 String macroName = xmlContent.getStringValue( 871 m_cms, 872 formatterValue.getPath() + "/" + CmsMacroFormatterResolver.N_MACRO_NAME, 873 CmsLocaleManager.MASTER_LOCALE); 874 result.put(macroName, formatterId); 875 } 876 return result; 877 } 878 879}