001/* 002 * Copyright 2012 Anyware Services 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 017package org.ametys.cms.transformation.xslt; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Properties; 027 028import javax.xml.parsers.DocumentBuilder; 029import javax.xml.parsers.DocumentBuilderFactory; 030import javax.xml.transform.OutputKeys; 031import javax.xml.transform.Transformer; 032import javax.xml.transform.TransformerConfigurationException; 033import javax.xml.transform.TransformerException; 034import javax.xml.transform.TransformerFactory; 035import javax.xml.transform.URIResolver; 036import javax.xml.transform.dom.DOMResult; 037import javax.xml.transform.sax.SAXTransformerFactory; 038import javax.xml.transform.sax.TransformerHandler; 039import javax.xml.transform.stream.StreamSource; 040 041import org.apache.avalon.framework.context.Context; 042import org.apache.avalon.framework.context.ContextException; 043import org.apache.avalon.framework.logger.LogEnabled; 044import org.apache.avalon.framework.logger.Logger; 045import org.apache.avalon.framework.service.ServiceException; 046import org.apache.avalon.framework.service.ServiceManager; 047import org.apache.cocoon.components.ContextHelper; 048import org.apache.cocoon.components.source.SourceUtil; 049import org.apache.cocoon.environment.Request; 050import org.apache.cocoon.xml.XMLUtils; 051import org.apache.commons.lang.StringUtils; 052import org.apache.commons.lang3.ArrayUtils; 053import org.apache.commons.lang3.LocaleUtils; 054import org.apache.excalibur.source.Source; 055import org.w3c.dom.Document; 056import org.w3c.dom.Element; 057import org.w3c.dom.Node; 058import org.w3c.dom.NodeList; 059import org.xml.sax.SAXException; 060 061import org.ametys.cms.content.ContentHelper; 062import org.ametys.cms.contenttype.ContentType; 063import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 064import org.ametys.cms.data.RichText; 065import org.ametys.cms.data.RichTextHelper; 066import org.ametys.cms.repository.Content; 067import org.ametys.cms.tag.CMSTag; 068import org.ametys.cms.tag.ColorableTag; 069import org.ametys.cms.tag.Tag; 070import org.ametys.cms.tag.TagProviderExtensionPoint; 071import org.ametys.cms.transformation.dom.TagElement; 072import org.ametys.core.util.dom.AmetysNodeList; 073import org.ametys.core.util.dom.EmptyElement; 074import org.ametys.core.util.dom.MapElement; 075import org.ametys.core.util.dom.StringElement; 076import org.ametys.plugins.explorer.resources.Resource; 077import org.ametys.plugins.explorer.resources.ResourceCollection; 078import org.ametys.plugins.explorer.resources.dom.ResourceCollectionElement; 079import org.ametys.plugins.repository.AmetysObjectResolver; 080import org.ametys.plugins.repository.AmetysRepositoryException; 081import org.ametys.plugins.repository.UnknownAmetysObjectException; 082import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 083import org.ametys.plugins.repository.model.RepositoryDataContext; 084import org.ametys.plugins.repository.version.VersionAwareAmetysObject; 085import org.ametys.runtime.model.exception.BadDataPathCardinalityException; 086import org.ametys.runtime.model.type.DataContext; 087 088/** 089 * Helper component to be used from XSL stylesheets. 090 */ 091public class AmetysXSLTHelper extends org.ametys.core.util.AmetysXSLTHelper implements LogEnabled 092{ 093 /** The Ametys object resolver */ 094 protected static AmetysObjectResolver _ametysObjectResolver; 095 /** The content types extension point */ 096 protected static ContentTypeExtensionPoint _cTypeExtensionPoint; 097 /** The tags provider */ 098 protected static TagProviderExtensionPoint _tagProviderExtPt; 099 /** Helper for content */ 100 protected static ContentHelper _contentHelper; 101 /** The avalon context */ 102 protected static Context _context; 103 /** The logger */ 104 protected static Logger _logger; 105 /** The sax parser */ 106 protected static RichTextHelper _richTextHelper; 107 108 @Override 109 public void contextualize(Context context) throws ContextException 110 { 111 super.contextualize(context); 112 _context = context; 113 } 114 115 @Override 116 public void enableLogging(Logger logger) 117 { 118 _logger = logger; 119 } 120 121 @Override 122 public void service(ServiceManager manager) throws ServiceException 123 { 124 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 125 _cTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 126 _tagProviderExtPt = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE); 127 _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE); 128 _richTextHelper = (RichTextHelper) manager.lookup(RichTextHelper.ROLE); 129 } 130 131 /* ------------------------ */ 132 /* Content methods */ 133 /* ------------------------ */ 134 135 /** 136 * Get the content types of a content 137 * @param contentId The content id 138 * @return The content type or empty if the content does not exist 139 */ 140 public static NodeList contentTypes(String contentId) 141 { 142 ArrayList<StringElement> contentTypes = new ArrayList<>(); 143 144 try 145 { 146 Content content = _ametysObjectResolver.resolveById(contentId); 147 148 try 149 { 150 for (String id : content.getTypes()) 151 { 152 contentTypes.add(new StringElement("content-type", "id", id)); 153 } 154 } 155 catch (AmetysRepositoryException e) 156 { 157 _logger.error("Can not get type of content with id '" + contentId + "'", e); 158 } 159 } 160 catch (UnknownAmetysObjectException e) 161 { 162 _logger.error("Can not get type of content with id '" + contentId + "'", e); 163 } 164 165 return new AmetysNodeList(contentTypes); 166 } 167 168 /** 169 * Get the mixins of a content 170 * @param contentId The content id 171 * @return The content type or empty if the content does not exist 172 */ 173 public static NodeList contentMixinTypes(String contentId) 174 { 175 ArrayList<StringElement> contentTypes = new ArrayList<>(); 176 177 try 178 { 179 Content content = _ametysObjectResolver.resolveById(contentId); 180 181 try 182 { 183 for (String id : content.getMixinTypes()) 184 { 185 contentTypes.add(new StringElement("mixin", "id", id)); 186 } 187 } 188 catch (AmetysRepositoryException e) 189 { 190 _logger.error("Can not get type of content with id '" + contentId + "'", e); 191 } 192 } 193 catch (UnknownAmetysObjectException e) 194 { 195 _logger.error("Can not get type of content with id '" + contentId + "'", e); 196 } 197 198 return new AmetysNodeList(contentTypes); 199 } 200 201 /** 202 * Determines if the content of given id is a entry of reference table 203 * @param contentId the content id 204 * @return true if the content type is a reference table 205 */ 206 public static boolean isReferenceTableContent(String contentId) 207 { 208 try 209 { 210 Content content = _ametysObjectResolver.resolveById(contentId); 211 return _contentHelper.isReferenceTable(content); 212 } 213 catch (UnknownAmetysObjectException e) 214 { 215 _logger.error("Can not get type of unknown content with id '" + contentId + "'", e); 216 return false; 217 } 218 } 219 220 /** 221 * Returns the current language for rendering. 222 * @return the current language for rendering. 223 */ 224 public static String lang() 225 { 226 Request request = ContextHelper.getRequest(_context); 227 return (String) request.getAttribute("renderingLanguage"); 228 } 229 230 /** 231 * Determines if there is a non-empty value for the data at the given path 232 * @param contentId The content id 233 * @param dataPath the path of data 234 * @return true if the data exists 235 */ 236 public static boolean hasValue(String contentId, String dataPath) 237 { 238 try 239 { 240 if (StringUtils.isEmpty(contentId) || StringUtils.isEmpty(dataPath)) 241 { 242 if (_logger.isDebugEnabled()) 243 { 244 _logger.debug("Can not check if content has a non-empty value: mandatory arguments content's id and/or attribute path are missing (" + contentId + ", " + dataPath + ")"); 245 } 246 return false; 247 } 248 249 Content content = _ametysObjectResolver.resolveById(contentId); 250 return content.hasValue(dataPath); 251 } 252 catch (UnknownAmetysObjectException | BadDataPathCardinalityException e) 253 { 254 if (_logger.isDebugEnabled()) 255 { 256 _logger.debug("Can not check if attribute at path '" + dataPath + "' exists and is not empty on content with id '" + contentId + "'", e); 257 } 258 return false; 259 } 260 } 261 262 /** 263 * Get the attribute of a content at the given path 264 * @param contentId The content id 265 * @param dataPath The data path 266 * @return The value into a "value" node or null if an error occurred 267 */ 268 public static NodeList contentAttribute(String contentId, String dataPath) 269 { 270 return contentAttribute(contentId, dataPath, null); 271 } 272 273 /** 274 * Get the attribute of a content at the given path 275 * @param contentId The content id 276 * @param dataPath The data path 277 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 278 * @return The value into a "value" node or null if an error occurred 279 */ 280 public static NodeList contentAttribute(String contentId, String dataPath, String lang) 281 { 282 try 283 { 284 Content content = _ametysObjectResolver.resolveById(contentId); 285 DataContext context = RepositoryDataContext.newInstance() 286 .withObject(content); 287 288 List<Node> values = _getNodeValues(content.getDataHolder(), dataPath, lang, context); 289 if (values != null) 290 { 291 return new AmetysNodeList(values); 292 } 293 } 294 catch (UnknownAmetysObjectException e) 295 { 296 _logger.error("Can not get attribute at path '" + dataPath + "' on unknown content with id '" + contentId + "'", e); 297 } 298 299 return null; 300 } 301 302 /** 303 * Get values of an attribute of a model aware data holder at the given path 304 * @param dataHolder the data holder 305 * @param dataPath The data path 306 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 307 * @return A Node for each values or null if an error occurred 308 */ 309 protected static List<Node> _getNodeValues(ModelAwareDataHolder dataHolder, String dataPath, String lang) 310 { 311 return _getNodeValues(dataHolder, dataPath, lang, DataContext.newInstance()); 312 } 313 314 /** 315 * Get values of an attribute of a model aware data holder at the given path 316 * @param dataHolder the data holder 317 * @param dataPath The data path 318 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 319 * @param dataContext The data context 320 * @return A Node for each values or null if an error occurred 321 */ 322 protected static List<Node> _getNodeValues(ModelAwareDataHolder dataHolder, String dataPath, String lang, DataContext dataContext) 323 { 324 325 if (dataHolder == null) 326 { 327 return null; 328 } 329 330 try 331 { 332 SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 333 TransformerHandler th = saxTransformerFactory.newTransformerHandler(); 334 335 DOMResult result = new DOMResult(); 336 th.setResult(result); 337 338 th.startDocument(); 339 XMLUtils.startElement(th, "value"); 340 341 Locale locale = StringUtils.isEmpty(lang) ? null : LocaleUtils.toLocale(lang); 342 dataHolder.dataToSAX(th, dataPath, dataContext.cloneContext().withLocale(locale).withEmptyValues(false)); 343 344 XMLUtils.endElement(th, "value"); 345 th.endDocument(); 346 347 List<Node> values = new ArrayList<>(); 348 349 // #getChildNodes() returns a NodeList that contains the value(s) saxed 350 // we cannot returns directly this NodeList because saxed values should be wrapped into a <value> tag. 351 NodeList childNodes = result.getNode().getFirstChild().getChildNodes(); 352 for (int i = 0; i < childNodes.getLength(); i++) 353 { 354 Node n = childNodes.item(i); 355 values.add(n); 356 } 357 358 return values; 359 } 360 catch (BadDataPathCardinalityException e) 361 { 362 _logger.error("Unable to get attribute at path '" + dataPath + "'. Path is invalid.", e); 363 } 364 catch (TransformerConfigurationException | SAXException e) 365 { 366 _logger.error("Fail to sax attribute at path '" + dataPath + "'", e); 367 } 368 catch (Exception e) 369 { 370 _logger.error("An error occurred, impossible to get attribute at path '" + dataPath + "'", e); 371 } 372 373 return null; 374 } 375 376 /** 377 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of the current content. 378 * @return an Element containing the attachments of the current content as {@link Resource}s. 379 */ 380 public static Node contentAttachments() 381 { 382 Request request = ContextHelper.getRequest(_context); 383 384 Content content = (Content) request.getAttribute(Content.class.getName()); 385 386 return contentAttachments(content); 387 } 388 389 /** 390 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of a given content. 391 * @param contentId the content ID. 392 * @return an Element containing the attachments of the given content as {@link Resource}s. 393 */ 394 public static Node contentAttachments(String contentId) 395 { 396 Content content = _ametysObjectResolver.resolveById(contentId); 397 398 return contentAttachments(content); 399 } 400 401 /** 402 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of a given content. 403 * @param content the content. 404 * @return an Element containing the attachments of the given content as {@link Resource}s. 405 */ 406 private static Node contentAttachments(Content content) 407 { 408 if (content == null) 409 { 410 return null; 411 } 412 413 ResourceCollection collection = content.getRootAttachments(); 414 415 return collection != null ? new ResourceCollectionElement(collection) : new EmptyElement("collection"); 416 } 417 418 /** 419 * Set the content of given id in request attribute 420 * @param contentId the id of content 421 */ 422 public static void setCurrentContent(String contentId) 423 { 424 setCurrentContent(contentId, null); 425 } 426 427 /** 428 * Set the content of given id and version in request attribute 429 * @param contentId the id of content 430 * @param versionLabel The version label 431 */ 432 public static void setCurrentContent(String contentId, String versionLabel) 433 { 434 Request request = ContextHelper.getRequest(_context); 435 436 Content content = _ametysObjectResolver.resolveById(contentId); 437 438 if (StringUtils.isNotEmpty(versionLabel) && content instanceof VersionAwareAmetysObject) 439 { 440 String[] allLabels = ((VersionAwareAmetysObject) content).getAllLabels(); 441 if (ArrayUtils.contains(allLabels, versionLabel)) 442 { 443 ((VersionAwareAmetysObject) content).switchToLabel(versionLabel); 444 } 445 } 446 447 request.setAttribute(Content.class.getName(), content); 448 449 } 450 451 //************************* 452 // Tag methods 453 //************************* 454 455 /** 456 * Returns all tags of the current content. 457 * @return a list of tags. 458 */ 459 public static NodeList contentTags() 460 { 461 Request request = ContextHelper.getRequest(_context); 462 463 Content content = (Content) request.getAttribute(Content.class.getName()); 464 return _contentTags(content); 465 } 466 467 /** 468 * Returns all tags of the given content 469 * @param contentId The identifier of the content 470 * @return a list of tags. 471 */ 472 public static NodeList contentTags(String contentId) 473 { 474 try 475 { 476 Content content = _ametysObjectResolver.resolveById(contentId); 477 return _contentTags(content); 478 } 479 catch (AmetysRepositoryException e) 480 { 481 _logger.warn("Cannot get tags for content '" + contentId + "'", e); 482 } 483 484 return null; 485 } 486 487 /** 488 * Returns all tags of the given content 489 * @param content The content 490 * @return a list of tags. 491 */ 492 protected static NodeList _contentTags(Content content) 493 { 494 if (content == null) 495 { 496 return null; 497 } 498 499 List<TagElement> list = new ArrayList<>(); 500 501 for (String tag : content.getTags()) 502 { 503 list.add(new TagElement(tag)); 504 } 505 506 return new AmetysNodeList(list); 507 } 508 509 /** 510 * Get the name of the parent of a tag. 511 * @param siteName the site name 512 * @param tagName the tag's name 513 * @return The id of parent or empty if not found 514 */ 515 public static String tagParent(String siteName, String tagName) 516 { 517 Map<String, Object> contextParameters = new HashMap<>(); 518 contextParameters.put("siteName", siteName); 519 520 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 521 if (tag == null) 522 { 523 return StringUtils.EMPTY; 524 } 525 526 String parentName = tag.getParentName(); 527 return parentName != null ? parentName : StringUtils.EMPTY; 528 } 529 530 /** 531 * Get the path of a tag. The path contains the tag's parents seprated by '/'. 532 * @param siteName The site name 533 * @param tagName The unique tag's name 534 * @return The tag's path or empty string if tag does not exist 535 */ 536 public static String tagPath (String siteName, String tagName) 537 { 538 Map<String, Object> contextParameters = new HashMap<>(); 539 contextParameters.put("siteName", siteName); 540 541 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 542 if (tag == null) 543 { 544 return StringUtils.EMPTY; 545 } 546 547 String path = tagName; 548 549 Tag parentTag = tag.getParent(); 550 while (parentTag != null) 551 { 552 path = parentTag.getName() + "/" + path; 553 parentTag = parentTag.getParent(); 554 } 555 556 return path; 557 } 558 559 /** 560 * Get the label of a tag 561 * @param siteName the current site 562 * @param tagName the name of the tag 563 * @param lang the lang (if i18n tag) 564 * @return the label of the tag or empty if it cannot be found 565 */ 566 public static String tagLabel(String siteName, String tagName, String lang) 567 { 568 Map<String, Object> contextParameters = new HashMap<>(); 569 contextParameters.put("siteName", siteName); 570 571 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 572 return tag == null ? "" : _i18nUtils.translate(tag.getTitle(), lang); 573 } 574 575 /** 576 * Get the description of a tag 577 * @param siteName the current site 578 * @param tagName the name of the tag 579 * @param lang the lang (if i18n tag) 580 * @return the label of the tag or empty if it cannot be found 581 */ 582 public static String tagDescription(String siteName, String tagName, String lang) 583 { 584 Map<String, Object> contextParameters = new HashMap<>(); 585 contextParameters.put("siteName", siteName); 586 587 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 588 return tag == null ? "" : _i18nUtils.translate(tag.getDescription(), lang); 589 } 590 591 /** 592 * Get the visibility of a tag 593 * @param siteName the current site 594 * @param tagName the name of the tag 595 * @return the lower-cased visibility of the tag ("public" or "private") 596 */ 597 public static String tagVisibility(String siteName, String tagName) 598 { 599 Map<String, Object> contextParameters = new HashMap<>(); 600 contextParameters.put("siteName", siteName); 601 602 CMSTag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 603 return tag == null ? "" : tag.getVisibility().toString().toLowerCase(); 604 } 605 606 /** 607 * Get the color (main and text) of a tag 608 * @param siteName the current site 609 * @param tagName the name of the tag 610 * @return the the color (main and text) of a tag 611 */ 612 public static MapElement tagColor(String siteName, String tagName) 613 { 614 Map<String, Object> contextParameters = new HashMap<>(); 615 contextParameters.put("siteName", siteName); 616 617 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 618 if (tag instanceof ColorableTag colorTag) 619 { 620 String color = colorTag.getColor(true); 621 Map<String, String> map = colorTag.getColorComponent().getColors().get(color); 622 return map != null ? new MapElement("color", map) : null; 623 } 624 625 return null; 626 } 627 628 /* ----------------------------- */ 629 /* Content type methods */ 630 /* ----------------------------- */ 631 632 /** 633 * Returns all tags of a content type 634 * @param contentTypeId The id of the content type 635 * @return a list of tags. 636 */ 637 public static NodeList contentTypeTags(String contentTypeId) 638 { 639 ArrayList<TagElement> tags = new ArrayList<>(); 640 641 try 642 { 643 ContentType cType = _cTypeExtensionPoint.getExtension(contentTypeId); 644 if (cType != null) 645 { 646 for (String tag : cType.getTags()) 647 { 648 tags.add(new TagElement(tag)); 649 } 650 } 651 else 652 { 653 _logger.error("Can not get tags of unknown content type of id '" + contentTypeId + "'"); 654 } 655 656 } 657 catch (AmetysRepositoryException e) 658 { 659 _logger.error("Can not get tags of content type of id '" + contentTypeId + "'", e); 660 } 661 662 return new AmetysNodeList(tags); 663 } 664 665 /** 666 * Get the excerpt of content from the given richtext attribute 667 * @param contentId the id of content 668 * @param attributePath the attribute path of rich text attribute 669 * @param limit the max length for content excerpt 670 * @return the excerpt 671 */ 672 public static String contentExcerpt(String contentId, String attributePath, int limit) 673 { 674 Content content = _ametysObjectResolver.resolveById(contentId); 675 676 if (content.hasValue(attributePath)) 677 { 678 RichText richText = content.getValue(attributePath); 679 return _richTextHelper.richTextToString(richText, limit); 680 } 681 682 return org.apache.commons.lang3.StringUtils.EMPTY; 683 } 684 685 /** 686 * Get the HTML view of a content 687 * @param contentId the id of content 688 * @return the content html view wrapped into a <content> tag 689 */ 690 public static Node getContentView(String contentId) 691 { 692 return getContentView(contentId, null, 1, null, false); 693 } 694 695 /** 696 * Get the HTML view of a content with offset on headings 697 * @param contentId the id of content 698 * @param startHeadingsLevel The start level for headings (h1, h2, h3, ...). For example, set to 2 so that the highest level headings are <h2>. Set to 1 for no offset. 699 * @return the content html view wrapped into a <content> tag 700 */ 701 public static Node getContentView(String contentId, int startHeadingsLevel) 702 { 703 return getContentView(contentId, null, startHeadingsLevel, null, false); 704 } 705 706 /** 707 * Get the HTML view of a content 708 * @param contentId the id of content 709 * @param viewName The content view name 710 * @return the content html view wrapped into a <content> tag 711 */ 712 public static Node getContentView(String contentId, String viewName) 713 { 714 return getContentView(contentId, viewName, 1, null, false); 715 } 716 717 /** 718 * Get the HTML view of a content 719 * @param contentId the id of content 720 * @param viewName The content view name 721 * @param startHeadingsLevel The start level for headings (h1, h2, h3, ...). For example, set to 2 so that the highest level headings are <h2>. Set to 1 for no offset. 722 * @return the content html view wrapped into a <content> tag 723 */ 724 public static Node getContentView(String contentId, String viewName, int startHeadingsLevel) 725 { 726 return getContentView(contentId, viewName, startHeadingsLevel, null, false); 727 } 728 729 /** 730 * Get the HTML view of a content with offset on headings 731 * @param contentId the id of content 732 * @param viewName The content view name 733 * @param lang the language. Can be null. Useful only if the content has a multilingual title. 734 * @param startHeadingsLevel The start level for headings (h1, h2, h3, ...). For example, set to 2 so that the highest level headings are <h2>. Set to 1 for no offset. 735 * @param checkReadAccess Set to <code>true</code> to check the read access on content. Be careful, do not use with <code>true</code> on a cacheable element. 736 * @return the content html view wrapped into a <content> tag 737 */ 738 public static Node getContentView(String contentId, String viewName, int startHeadingsLevel, String lang, boolean checkReadAccess) 739 { 740 Content content = _ametysObjectResolver.resolveById(contentId); 741 742 if (checkReadAccess && !_rightManager.currentUserHasReadAccess(content)) 743 { 744 _logger.warn("Current user is not authorized to see content of id '" + contentId + "'. AmetysXSLHelper#getContentView will return null element"); 745 return null; 746 } 747 748 Locale requestedLocale = StringUtils.isNotEmpty(lang) ? LocaleUtils.toLocale(lang) : null; 749 750 DocumentBuilder builder = null; 751 Source source = null; 752 753 String uri = _contentHelper.getContentHtmlViewUrl(content, viewName); 754 755 try 756 { 757 source = _sourceResolver.resolveURI(uri); 758 759 Source src = null; 760 try 761 { 762 // Wrap HTML view into a <content> tag and move titles hierarchy if needed 763 src = _sourceResolver.resolveURI("plugin:cms://stylesheets/content/content2htmlview.xsl"); 764 try (InputStream is = src.getInputStream()) 765 { 766 SAXTransformerFactory tFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 767 768 // Set uri resolver to resolve import 769 tFactory.setURIResolver(new URIResolver() 770 { 771 public javax.xml.transform.Source resolve(String href, String base) throws TransformerException 772 { 773 try 774 { 775 Source resolvedSource = _sourceResolver.resolveURI(href); 776 return new StreamSource(resolvedSource.getInputStream()); 777 } 778 catch (IOException e) 779 { 780 throw new TransformerException(e); 781 } 782 } 783 }); 784 785 TransformerHandler transformerHandler = tFactory.newTransformerHandler(new StreamSource(is)); 786 Transformer transformer = transformerHandler.getTransformer(); 787 788 Properties format = new Properties(); 789 format.put(OutputKeys.METHOD, "xml"); 790 format.put(OutputKeys.ENCODING, "UTF-8"); 791 792 transformer.setOutputProperties(format); 793 794 transformer.setParameter("contentId", content.getId()); 795 transformer.setParameter("contentName", content.getName()); 796 transformer.setParameter("contentTitle", content.getTitle(requestedLocale)); 797 if (content.getLanguage() != null) 798 { 799 transformer.setParameter("contentLanguage", content.getLanguage()); 800 } 801 transformer.setParameter("headingLevel", startHeadingsLevel); 802 803 builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 804 Document document = builder.newDocument(); 805 DOMResult result = new DOMResult(document); 806 807 transformerHandler.setResult(result); 808 SourceUtil.toSAX(source, transformerHandler); 809 810 return result.getNode(); 811 } 812 } 813 finally 814 { 815 _sourceResolver.release(src); 816 } 817 } 818 catch (Exception e) 819 { 820 _logger.error("Fail to get HTML view of content " + contentId, e); 821 return null; 822 } 823 finally 824 { 825 _sourceResolver.release(source); 826 } 827 } 828}