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 String lang = (String) request.getAttribute("renderingLanguage"); 228 if (StringUtils.isBlank(lang)) 229 { 230 Locale navigatorLocale = request.getLocale(); 231 lang = navigatorLocale != null ? navigatorLocale.getLanguage() : "en"; 232 } 233 234 return lang; 235 } 236 237 /** 238 * Determines if there is a non-empty value for the data at the given path 239 * @param contentId The content id 240 * @param dataPath the path of data 241 * @return true if the data exists 242 */ 243 public static boolean hasValue(String contentId, String dataPath) 244 { 245 try 246 { 247 if (StringUtils.isEmpty(contentId) || StringUtils.isEmpty(dataPath)) 248 { 249 if (_logger.isDebugEnabled()) 250 { 251 _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 + ")"); 252 } 253 return false; 254 } 255 256 Content content = _ametysObjectResolver.resolveById(contentId); 257 return content.hasValue(dataPath); 258 } 259 catch (UnknownAmetysObjectException | BadDataPathCardinalityException e) 260 { 261 if (_logger.isDebugEnabled()) 262 { 263 _logger.debug("Can not check if attribute at path '" + dataPath + "' exists and is not empty on content with id '" + contentId + "'", e); 264 } 265 return false; 266 } 267 } 268 269 /** 270 * Get the attribute of a content at the given path 271 * @param contentId The content id 272 * @param dataPath The data path 273 * @return The value into a "value" node or null if an error occurred 274 */ 275 public static NodeList contentAttribute(String contentId, String dataPath) 276 { 277 return contentAttribute(contentId, dataPath, null); 278 } 279 280 /** 281 * Get the attribute of a content at the given path 282 * @param contentId The content id 283 * @param dataPath The data path 284 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 285 * @return The value into a "value" node or null if an error occurred 286 */ 287 public static NodeList contentAttribute(String contentId, String dataPath, String lang) 288 { 289 try 290 { 291 Content content = _ametysObjectResolver.resolveById(contentId); 292 DataContext context = RepositoryDataContext.newInstance() 293 .withObject(content); 294 295 List<Node> values = _getNodeValues(content.getDataHolder(), dataPath, lang, context); 296 if (values != null) 297 { 298 return new AmetysNodeList(values); 299 } 300 } 301 catch (UnknownAmetysObjectException e) 302 { 303 _logger.error("Can not get attribute at path '" + dataPath + "' on unknown content with id '" + contentId + "'", e); 304 } 305 306 return null; 307 } 308 309 /** 310 * Get values of an attribute of a model aware data holder at the given path 311 * @param dataHolder the data holder 312 * @param dataPath The data path 313 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 314 * @return A Node for each values or null if an error occurred 315 */ 316 protected static List<Node> _getNodeValues(ModelAwareDataHolder dataHolder, String dataPath, String lang) 317 { 318 return _getNodeValues(dataHolder, dataPath, lang, DataContext.newInstance()); 319 } 320 321 /** 322 * Get values of an attribute of a model aware data holder at the given path 323 * @param dataHolder the data holder 324 * @param dataPath The data path 325 * @param lang The language for localized attribute. Can be null for non-localized attribute or to get the values for all existing locales. 326 * @param dataContext The data context 327 * @return A Node for each values or null if an error occurred 328 */ 329 protected static List<Node> _getNodeValues(ModelAwareDataHolder dataHolder, String dataPath, String lang, DataContext dataContext) 330 { 331 332 if (dataHolder == null) 333 { 334 return null; 335 } 336 337 try 338 { 339 SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 340 TransformerHandler th = saxTransformerFactory.newTransformerHandler(); 341 342 DOMResult result = new DOMResult(); 343 th.setResult(result); 344 345 th.startDocument(); 346 XMLUtils.startElement(th, "value"); 347 348 Locale locale = StringUtils.isEmpty(lang) ? null : LocaleUtils.toLocale(lang); 349 dataHolder.dataToSAX(th, dataPath, dataContext.cloneContext().withLocale(locale).withEmptyValues(false)); 350 351 XMLUtils.endElement(th, "value"); 352 th.endDocument(); 353 354 List<Node> values = new ArrayList<>(); 355 356 // #getChildNodes() returns a NodeList that contains the value(s) saxed 357 // we cannot returns directly this NodeList because saxed values should be wrapped into a <value> tag. 358 NodeList childNodes = result.getNode().getFirstChild().getChildNodes(); 359 for (int i = 0; i < childNodes.getLength(); i++) 360 { 361 Node n = childNodes.item(i); 362 values.add(n); 363 } 364 365 return values; 366 } 367 catch (BadDataPathCardinalityException e) 368 { 369 _logger.error("Unable to get attribute at path '" + dataPath + "'. Path is invalid.", e); 370 } 371 catch (TransformerConfigurationException | SAXException e) 372 { 373 _logger.error("Fail to sax attribute at path '" + dataPath + "'", e); 374 } 375 catch (Exception e) 376 { 377 _logger.error("An error occurred, impossible to get attribute at path '" + dataPath + "'", e); 378 } 379 380 return null; 381 } 382 383 /** 384 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of the current content. 385 * @return an Element containing the attachments of the current content as {@link Resource}s. 386 */ 387 public static Node contentAttachments() 388 { 389 Request request = ContextHelper.getRequest(_context); 390 391 Content content = (Content) request.getAttribute(Content.class.getName()); 392 393 return contentAttachments(content); 394 } 395 396 /** 397 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of a given content. 398 * @param contentId the content ID. 399 * @return an Element containing the attachments of the given content as {@link Resource}s. 400 */ 401 public static Node contentAttachments(String contentId) 402 { 403 Content content = _ametysObjectResolver.resolveById(contentId); 404 405 return contentAttachments(content); 406 } 407 408 /** 409 * Returns a DOM {@link Element} containing {@link Resource}s representing the attachments of a given content. 410 * @param content the content. 411 * @return an Element containing the attachments of the given content as {@link Resource}s. 412 */ 413 private static Node contentAttachments(Content content) 414 { 415 if (content == null) 416 { 417 return null; 418 } 419 420 ResourceCollection collection = content.getRootAttachments(); 421 422 return collection != null ? new ResourceCollectionElement(collection) : new EmptyElement("collection"); 423 } 424 425 /** 426 * Set the content of given id in request attribute 427 * @param contentId the id of content 428 */ 429 public static void setCurrentContent(String contentId) 430 { 431 setCurrentContent(contentId, null); 432 } 433 434 /** 435 * Set the content of given id and version in request attribute 436 * @param contentId the id of content 437 * @param versionLabel The version label 438 */ 439 public static void setCurrentContent(String contentId, String versionLabel) 440 { 441 Request request = ContextHelper.getRequest(_context); 442 443 Content content = _ametysObjectResolver.resolveById(contentId); 444 445 if (StringUtils.isNotEmpty(versionLabel) && content instanceof VersionAwareAmetysObject) 446 { 447 String[] allLabels = ((VersionAwareAmetysObject) content).getAllLabels(); 448 if (ArrayUtils.contains(allLabels, versionLabel)) 449 { 450 ((VersionAwareAmetysObject) content).switchToLabel(versionLabel); 451 } 452 } 453 454 request.setAttribute(Content.class.getName(), content); 455 456 } 457 458 //************************* 459 // Tag methods 460 //************************* 461 462 /** 463 * Returns all tags of the current content. 464 * @return a list of tags. 465 */ 466 public static NodeList contentTags() 467 { 468 Request request = ContextHelper.getRequest(_context); 469 470 Content content = (Content) request.getAttribute(Content.class.getName()); 471 return _contentTags(content); 472 } 473 474 /** 475 * Returns all tags of the given content 476 * @param contentId The identifier of the content 477 * @return a list of tags. 478 */ 479 public static NodeList contentTags(String contentId) 480 { 481 try 482 { 483 Content content = _ametysObjectResolver.resolveById(contentId); 484 return _contentTags(content); 485 } 486 catch (AmetysRepositoryException e) 487 { 488 _logger.warn("Cannot get tags for content '" + contentId + "'", e); 489 } 490 491 return null; 492 } 493 494 /** 495 * Returns all tags of the given content 496 * @param content The content 497 * @return a list of tags. 498 */ 499 protected static NodeList _contentTags(Content content) 500 { 501 if (content == null) 502 { 503 return null; 504 } 505 506 List<TagElement> list = new ArrayList<>(); 507 508 for (String tag : content.getTags()) 509 { 510 list.add(new TagElement(tag)); 511 } 512 513 return new AmetysNodeList(list); 514 } 515 516 /** 517 * Get the name of the parent of a tag. 518 * @param siteName the site name 519 * @param tagName the tag's name 520 * @return The id of parent or empty if not found 521 */ 522 public static String tagParent(String siteName, String tagName) 523 { 524 Map<String, Object> contextParameters = new HashMap<>(); 525 contextParameters.put("siteName", siteName); 526 527 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 528 if (tag == null) 529 { 530 return StringUtils.EMPTY; 531 } 532 533 String parentName = tag.getParentName(); 534 return parentName != null ? parentName : StringUtils.EMPTY; 535 } 536 537 /** 538 * Get the path of a tag. The path contains the tag's parents seprated by '/'. 539 * @param siteName The site name 540 * @param tagName The unique tag's name 541 * @return The tag's path or empty string if tag does not exist 542 */ 543 public static String tagPath (String siteName, String tagName) 544 { 545 Map<String, Object> contextParameters = new HashMap<>(); 546 contextParameters.put("siteName", siteName); 547 548 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 549 if (tag == null) 550 { 551 return StringUtils.EMPTY; 552 } 553 554 String path = tagName; 555 556 Tag parentTag = tag.getParent(); 557 while (parentTag != null) 558 { 559 path = parentTag.getName() + "/" + path; 560 parentTag = parentTag.getParent(); 561 } 562 563 return path; 564 } 565 566 /** 567 * Get the label of a tag 568 * @param siteName the current site 569 * @param tagName the name of the tag 570 * @param lang the lang (if i18n tag) 571 * @return the label of the tag or empty if it cannot be found 572 */ 573 public static String tagLabel(String siteName, String tagName, String lang) 574 { 575 Map<String, Object> contextParameters = new HashMap<>(); 576 contextParameters.put("siteName", siteName); 577 578 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 579 return tag == null ? "" : _i18nUtils.translate(tag.getTitle(), lang); 580 } 581 582 /** 583 * Get the description of a tag 584 * @param siteName the current site 585 * @param tagName the name of the tag 586 * @param lang the lang (if i18n tag) 587 * @return the label of the tag or empty if it cannot be found 588 */ 589 public static String tagDescription(String siteName, String tagName, String lang) 590 { 591 Map<String, Object> contextParameters = new HashMap<>(); 592 contextParameters.put("siteName", siteName); 593 594 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 595 return tag == null ? "" : _i18nUtils.translate(tag.getDescription(), lang); 596 } 597 598 /** 599 * Get the visibility of a tag 600 * @param siteName the current site 601 * @param tagName the name of the tag 602 * @return the lower-cased visibility of the tag ("public" or "private") 603 */ 604 public static String tagVisibility(String siteName, String tagName) 605 { 606 Map<String, Object> contextParameters = new HashMap<>(); 607 contextParameters.put("siteName", siteName); 608 609 CMSTag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 610 return tag == null ? "" : tag.getVisibility().toString().toLowerCase(); 611 } 612 613 /** 614 * Get the color (main and text) of a tag 615 * @param siteName the current site 616 * @param tagName the name of the tag 617 * @return the the color (main and text) of a tag 618 */ 619 public static MapElement tagColor(String siteName, String tagName) 620 { 621 Map<String, Object> contextParameters = new HashMap<>(); 622 contextParameters.put("siteName", siteName); 623 624 Tag tag = _tagProviderExtPt.getTag(tagName, contextParameters); 625 if (tag != null && tag instanceof ColorableTag colorTag) 626 { 627 String color = colorTag.getColor(true); 628 Map<String, String> map = colorTag.getColorComponent().getColors().get(color); 629 return map != null ? new MapElement("color", map) : null; 630 } 631 632 return null; 633 } 634 635 /* ----------------------------- */ 636 /* Content type methods */ 637 /* ----------------------------- */ 638 639 /** 640 * Returns all tags of a content type 641 * @param contentTypeId The id of the content type 642 * @return a list of tags. 643 */ 644 public static NodeList contentTypeTags(String contentTypeId) 645 { 646 ArrayList<TagElement> tags = new ArrayList<>(); 647 648 try 649 { 650 ContentType cType = _cTypeExtensionPoint.getExtension(contentTypeId); 651 if (cType != null) 652 { 653 for (String tag : cType.getTags()) 654 { 655 tags.add(new TagElement(tag)); 656 } 657 } 658 else 659 { 660 _logger.error("Can not get tags of unknown content type of id '" + contentTypeId + "'"); 661 } 662 663 } 664 catch (AmetysRepositoryException e) 665 { 666 _logger.error("Can not get tags of content type of id '" + contentTypeId + "'", e); 667 } 668 669 return new AmetysNodeList(tags); 670 } 671 672 /** 673 * Get the excerpt of content from the given richtext attribute 674 * @param contentId the id of content 675 * @param attributePath the attribute path of rich text attribute 676 * @param limit the max length for content excerpt 677 * @return the excerpt 678 */ 679 public static String contentExcerpt(String contentId, String attributePath, int limit) 680 { 681 Content content = _ametysObjectResolver.resolveById(contentId); 682 683 if (content.hasValue(attributePath)) 684 { 685 RichText richText = content.getValue(attributePath); 686 return _richTextHelper.richTextToString(richText, limit); 687 } 688 689 return org.apache.commons.lang3.StringUtils.EMPTY; 690 } 691 692 /** 693 * Get the HTML view of a content 694 * @param contentId the id of content 695 * @return the content html view wrapped into a <content> tag 696 */ 697 public static Node getContentView(String contentId) 698 { 699 return getContentView(contentId, null, 1, null, false); 700 } 701 702 /** 703 * Get the HTML view of a content with offset on headings 704 * @param contentId the id of content 705 * @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. 706 * @return the content html view wrapped into a <content> tag 707 */ 708 public static Node getContentView(String contentId, int startHeadingsLevel) 709 { 710 return getContentView(contentId, null, startHeadingsLevel, null, false); 711 } 712 713 /** 714 * Get the HTML view of a content 715 * @param contentId the id of content 716 * @param viewName The content view name 717 * @return the content html view wrapped into a <content> tag 718 */ 719 public static Node getContentView(String contentId, String viewName) 720 { 721 return getContentView(contentId, viewName, 1, null, false); 722 } 723 724 /** 725 * Get the HTML view of a content 726 * @param contentId the id of content 727 * @param viewName The content view name 728 * @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. 729 * @return the content html view wrapped into a <content> tag 730 */ 731 public static Node getContentView(String contentId, String viewName, int startHeadingsLevel) 732 { 733 return getContentView(contentId, viewName, startHeadingsLevel, null, false); 734 } 735 736 /** 737 * Get the HTML view of a content with offset on headings 738 * @param contentId the id of content 739 * @param viewName The content view name 740 * @param lang the language. Can be null. Useful only if the content has a multilingual title. 741 * @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. 742 * @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. 743 * @return the content html view wrapped into a <content> tag 744 */ 745 public static Node getContentView(String contentId, String viewName, int startHeadingsLevel, String lang, boolean checkReadAccess) 746 { 747 Content content = _ametysObjectResolver.resolveById(contentId); 748 749 if (checkReadAccess && !_rightManager.currentUserHasReadAccess(content)) 750 { 751 _logger.warn("Current user is not authorized to see content of id '" + contentId + "'. AmetysXSLHelper#getContentView will return null element"); 752 return null; 753 } 754 755 Locale requestedLocale = StringUtils.isNotEmpty(lang) ? LocaleUtils.toLocale(lang) : null; 756 757 DocumentBuilder builder = null; 758 Source source = null; 759 760 String uri = _contentHelper.getContentHtmlViewUrl(content, viewName); 761 762 try 763 { 764 source = _sourceResolver.resolveURI(uri); 765 766 Source src = null; 767 try 768 { 769 // Wrap HTML view into a <content> tag and move titles hierarchy if needed 770 src = _sourceResolver.resolveURI("plugin:cms://stylesheets/content/content2htmlview.xsl"); 771 try (InputStream is = src.getInputStream()) 772 { 773 SAXTransformerFactory tFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); 774 775 // Set uri resolver to resolve import 776 tFactory.setURIResolver(new URIResolver() 777 { 778 public javax.xml.transform.Source resolve(String href, String base) throws TransformerException 779 { 780 try 781 { 782 Source resolvedSource = _sourceResolver.resolveURI(href); 783 return new StreamSource(resolvedSource.getInputStream()); 784 } 785 catch (IOException e) 786 { 787 throw new TransformerException(e); 788 } 789 } 790 }); 791 792 TransformerHandler transformerHandler = tFactory.newTransformerHandler(new StreamSource(is)); 793 Transformer transformer = transformerHandler.getTransformer(); 794 795 Properties format = new Properties(); 796 format.put(OutputKeys.METHOD, "xml"); 797 format.put(OutputKeys.ENCODING, "UTF-8"); 798 799 transformer.setOutputProperties(format); 800 801 transformer.setParameter("contentId", content.getId()); 802 transformer.setParameter("contentName", content.getName()); 803 transformer.setParameter("contentTitle", content.getTitle(requestedLocale)); 804 if (content.getLanguage() != null) 805 { 806 transformer.setParameter("contentLanguage", content.getLanguage()); 807 } 808 transformer.setParameter("headingLevel", startHeadingsLevel); 809 810 builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 811 Document document = builder.newDocument(); 812 DOMResult result = new DOMResult(document); 813 814 transformerHandler.setResult(result); 815 SourceUtil.toSAX(source, transformerHandler); 816 817 return result.getNode(); 818 } 819 } 820 finally 821 { 822 _sourceResolver.release(src); 823 } 824 } 825 catch (Exception e) 826 { 827 _logger.error("Fail to get HTML view of content " + contentId, e); 828 return null; 829 } 830 finally 831 { 832 _sourceResolver.release(source); 833 } 834 } 835}