001    package hirondelle.web4j.ui.tag;
002    
003    import java.io.IOException;
004    import javax.servlet.jsp.JspException;
005    import java.util.regex.*;
006    import hirondelle.web4j.ui.tag.TagHelper;
007    import hirondelle.web4j.util.Regex;
008    import hirondelle.web4j.util.Util;
009    
010    /**
011     Generate table rows which alternate in appearance, to increase legibility.
012    
013     <P>The body of this tag contains one or more <tt>TR</tt> tags. Each <tt>TR</tt> tag contains a 
014     <tt>class</tt> attribute, specifying a Cascading Style Sheet class. This tag 
015     will simply remove or update the <tt>class</tt> attribute for alternate occurrences of 
016     each <tt>TR</tt> tag found in its body.
017     
018     <P>If the optional <tt>altClass</tt> attribute is specified, then the <tt>class</tt> 
019     attribute of each <tt>TR</tt> is updated to an alternate value, instead of being removed. 
020    */
021    public final class AlternatingRow extends TagHelper {
022     
023      /**
024       Optional name of a CSS class. 
025       
026       <P>The CSS class for each <tt>TR</tt> tag found in the body of this tag will be 
027       updated to this value, for alternating rows. If this item is not specified, then the <tt>TR</tt>'s 
028       class attribute is simply removed instead of updated.
029       
030       @param aAltClass must have content.
031      */
032      public void setAltClass(String aAltClass){
033        checkForContent("AltClass", aAltClass);
034        fAltClass = aAltClass;
035      }
036      
037      /**
038       For each <tt>TR</tt> tag found in the body, remove or update the <tt>class</tt> attribute.
039       
040       <P>If no <tt>altClass</tt> is specified, then the <tt>class</tt> attribute is simply removed entirely. 
041       Otherwise, it updated to the <tt>altClass</tt> value.
042      */
043      protected String getEmittedText(String aOriginalBody) throws JspException, IOException {
044        StringBuffer result = new StringBuffer();
045        Matcher matcher = TR_PATTERN.matcher(aOriginalBody);
046        int matchIdx = 0;
047        while (matcher.find()){
048          ++ matchIdx;
049          if( isEvenRow(matchIdx) ){
050            matcher.appendReplacement(result, getReplacement(matcher));
051          }
052          else {
053            String noChange = matcher.group(0);
054            matcher.appendReplacement(result, noChange);
055          }
056        }
057        matcher.appendTail(result);
058        return result.toString();
059      }
060      
061      // PRIVATE //
062      private String fAltClass;
063      private static final Pattern TR_PATTERN = Pattern.compile("<tr" + "("+ Regex.ALL_BUT_END_OF_TAG + ")" + "class=" + Regex.QUOTED_ATTR + "("+ Regex.ALL_BUT_END_OF_TAG + ")"+ ">", Pattern.CASE_INSENSITIVE);
064      
065      private String getReplacement(Matcher aMatcher){
066        //construct replacement: <TR + G1 + class='G2' + G3 + >
067        StringBuilder result = new StringBuilder("<TR" + aMatcher.group(1)); 
068        if( Util.textHasContent(fAltClass) ){
069          result.append("class='" + fAltClass + "'"); 
070        }
071        result.append(aMatcher.group(3) + ">");
072        return result.toString();
073      }
074      
075      private boolean isEvenRow(int aMatchIdx){
076        return aMatchIdx % 2 == 0;
077      }
078    }