3.3Handle text with TinyRichtextParts

The richtext in a document is stored in a property of type text. The text property value is a String beeing almost normal HTML that could be processed by a DOM parser. Only links and components are not standard HTML and have to be transformed into either standard HTML for a website or other formats for other content types.

The href attribute of the A (anchor) tag has encoded either an external URL or a document ID as link target. Also a link type and optional additional parameters can be found there. From the document ID the Generator can produce an URL.

TinyRichtextParts

The app.cmsworks.util.text.tiny.TinyRichtextParts provide a lightweight but powerful way of handling various text manipulations.

Constructor

After giving the the text into the constructor the TinyRichtextParts creates a list of TinyRichtextPart objects representing a text part, a tag part (opening or closing) or a text component part.

The structure is not a tree, just a list referencing the different text parts in order. This list is not to be changed by removing, adding or exchanging elements.

An opening tag part is referencing it's closing part, contains a level.

Common functions of all TinyRichtextPart types

setText(String)

To replace the origin text simply set a new text to be produced in the final product

setTextBefore(String), setTextAfter(String), addTextBefore(String), addTextAfter(String)

Add some text before/after a text part.

setDoProduce(boolean)

To ignore this text part for the final product call setDoProduce(false). In this case also before and after Strings of this part will not be produced.

TinyRichtextTextPart

It does not add any functionality to the common functions.

TinyRichtextTagPart

  • It contains the state if it's the opening or closing tag part or for example in case of a BR tag beeing both at the same time.
  • If it's the opening part it references its closing part
  • It contains a level info about the depth in the hierarchy of tags
  • It manages an Attribute list.

getName()

Returns the tag name

setName(String)

For setting a new tag name

isNamed(String)

Helper for searching tags with special names

isOpeningTagPart(), isClosingTagPart(), getLevel()

Returning the state

setDoProduce(boolean)

not only for this tag part but also for the closing tag part if it is referenced

getClosingTagPart()

Returns the closing text part or null if not existing

getAttributeNames(), getAttribute(String), setAttribute(String, String), removeAttribute(String)

Managing attributes of the tag

TinyRichtextComponentPart

getDocumentId()

If a component is to be rendered from a Document

getPosition()

The positioning of the component in the text (left/right/inline/centered)

setText(String)

After producing the component the component code (HTML in most cases) is inserted into the text here.

TinyRichtextParts

getTextParts()

Returns the full list of text parts as TinyRichtextPart objects.

getTextParts(TinyRichtextPart)

returns a sublist of this text part. If the given TinyRichtextPart is actually an opening TinyRichtextTagPart all text parts are returned beginning at the opening tag and ending at the closing tag.

build(), build(TinyRichtextPart), build(idxStart, idxEnd)

Joining the text parts to a new text as a result after handling links, text components and any other creative text manipulations.

getUnTaggedText(), getUnTaggedText(idxStart, idxEnd)

It returns the collected text from each TinyRichtextPartText making sure that a space is added between block element texts.

hasOnlyEmptyText()

It tests if a tag has no visible text - whitespaces including   are ignored.

To use the TinyRichtextParts for website texts more text handling is necessary especially to handle links created from the Generator. In this case a sub class is created for example in a util-textparts.jsf.

<%@page import="
                app.cmsworks.cms.document.DocumentModel,

                app.cmsworks.util.text.tiny.TinyRichtextParts,
                app.cmsworks.util.text.tiny.TinyRichtextPart,
                app.cmsworks.util.text.tiny.TinyRichtextTagPart,
                app.cmsworks.util.text.tiny.TinyRichtextTextPart,
                app.cmsworks.util.text.tiny.TinyRichtextComponentPart,
                app.cmsworks.util.text.tiny.TinyRichtextLinkTransformerHTML,
                
                java.util.ArrayList
               "
        pageEncoding="UTF-8"
%><%!

/** The subclass of TinyRichtextParts will handle links and components
 */
public class MyTinyRichtextParts extends TinyRichtextParts {

  /** Constructor parsing the given text 
   */
  public MyTinyRichtextParts(String text) {
    super(text);
  }
  
  /** Find all p elements with no component and no text inside to deaktivate them
   */
  public void deleteEmptyP() {
    // for each opening tag part named p
    for (TinyRichtextTagPart trpTag : getOpeningTagsNamed("p")) {
      // having no class attribute
      if (trpTag.getAttribute("class") == null) {
        // having no component and no text 
        if (hasNoComponentAndOnlyEmptyText(trpTag)) {
          // set doProduce=false for this tag and all content text parts
          removeAll(trpTag);
        }
      }
    }
  }
  
  /** Collecting and returning the component parts of the text
   */
  public TinyRichtextComponentPart[] getComponentParts() {
    // create a list to collect the component parts in
    ArrayList<TinyRichtextComponentPart> list = new ArrayList<TinyRichtextComponentPart>();
    // for each text part
    for (TinyRichtextPart trp : getTextParts()) {
      // if the text part is still to be produced
      if (trp.doProduce()) {
        // if the text part is a component part
        if (trp instanceof TinyRichtextComponentPart) {
          // add it into the list
          list.add((TinyRichtextComponentPart) trp);
        }
      }
    }
    // return the array from the list
    return list.toArray(new TinyRichtextComponentPart[list.size()]);
  }
  
  /** Working the links of the text
   */
  public void handleLinks(DocumentModel dmAny) throws Exception {
    // for each link
    for (TinyRichtextTagPart trpA : getOpeningTagsNamed("a")) {
      // create a Transformer
      TinyRichtextLinkTransformerHTML tran = new TinyRichtextLinkTransformerHTML(trpA);
      // set a resolved URL if it's an internal link
      tran.resolveUrl(dmAny.getUrlCreator());
      // do produce the link in propert HTML coding
      tran.transform();
    }
  }
  
}

%>

Exteding the TinyRichtextParts to handle links, removing empty lines and provide access to text components.

Within a page-xxx.jsp the MyRichtextParts can be used to render text into a HTML page.

<%@page import="
                app.cmsworks.cms.document.ErrorView,
                app.cmsworks.cms.document.HTMLErrorView,
                app.cmsworks.util.uilink.UILink,
                app.cmsworks.cms.document.ContentInclude
               "
        session="false"
        contentType="text/html;charset=UTF-8"
%><%@include file="includes/documentmodel.jsf"
%><%@include file="includes/util-texthandler.jsf" 
%><%

ErrorView errors = new HTMLErrorView();
DocumentModel dmPage = null;
UILink uiLink = new UILink(request);

String htmlText = "";

// fetch the sensible data
try {
  // get the DocumentModel from request
  dmPage = new DocumentModel(request, new Types());
  // init the errorview
  errors.setPreview(dmPage);

  // use the created MyTinyRichtextParts with the text to render
  MyTinyRichtextParts textPartsText = new MyTinyRichtextParts(dmPage.getString(Types.PT_TEXT));

  // transform the links 
  textPartsText.handleLinks(dmPage);

  // remove empty lines
  textPartsText.deleteEmptyP();

  // fill components
  for (TinyRichtextComponentPart compPart : textPartsText.getComponentParts()) {
    DocumentModel mrComponent = new DocumentModel(compPart.getDocumentId(), dmPage);
    ContentInclude include = null;
    if (mrComponent.isType(Types.RT_MEDIUM)) {
      include = new ContentInclude(dmPage, mrComponent, "cmp-medium.jsp", errors);
    }
    else if (mrComponent.isType(Types.RT_INFOBOX)) {
      include = new ContentInclude(dmPage, mrComponent, "cmp-infobox.jsp", errors);
    }
    ...
    String htmlComponent = null;
    if (include != null) {
      htmlComponent = include.getIncludeContent();
    }
    // if the include has errors or was not assigned by the conditions before the compPart will simply
    // be deactivated
    if (htmlComponent != null) {
      htmlComponent = "<span class=\"txtcomp " + compPart.getPosition() + "\">" + htmlComponent + "</span>";
      compPart.setText(htmlComponent);
    }
    else {
      compPart.setDoProduce(false);
      errors.add(errors.asUILink(mrComponent) + "This document is not usable as text component.");
    }
  }
  
  htmlText = textPartsText.build();
}
catch (Throwable t) {
  if (errors.exit(response, t, dmPage, this.getClass().getName())) {
    return;
  }
}
//now start the HTML-Output
%><!DOCTYPE html>
<html>
<head>
...
  <%= uiLink.getIncludes() %>
</head>
<body>
...
  <!-- placing the text and editable markers into the html code -->
  <div class="richtext"><%= htmlText %></div>
...
  <%= errors.render() %>
</body>
</html>

Using MyRichtextParts to render richtext into the HTML page.