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