Menu

#27 replaceValues and replaceAll of RichTextStringUtil throw IllegalArgumentException: Start and end index not in range.

2.0
open
nobody
None
2017-10-07
2014-05-23
Merci chao
No

Add this expression in a cell:

${java.lang.System.out.println('foo')} bar

and format 'foo' with a color (e.g. red).

Run the transformer and the following exception will be thrown:

java.lang.IllegalArgumentException: Start and end index not in range.
at org.apache.poi.xssf.usermodel.XSSFRichTextString.applyFont(XSSFRichTextString.java:140)
at net.sf.jett.util.RichTextStringUtil.formatString(RichTextStringUtil.java:476)
at net.sf.jett.util.RichTextStringUtil.createFormattedString(RichTextStringUtil.java:445)
at net.sf.jett.util.RichTextStringUtil.replaceValues(RichTextStringUtil.java:215)
at net.sf.jett.expression.Expression.replaceExpressions(Expression.java:543)
at net.sf.jett.expression.Expression.evaluateString(Expression.java:410)
at net.sf.jett.transform.CellTransformer.transform(CellTransformer.java:120)
......

I found that RichTextStringUtil.updateFormattingRuns does not handle the change correctly in some cases.

1 Attachments

Discussion

  • Merci chao

    Merci chao - 2014-05-23

    The attachments are the fixed version of RichTextStringUtil and the output for the test file above.
    The logic of RichTextStringUtil.updateFormattingRuns is improved, and the length of replacement target is required as a parameter.
    After calling updateFormattingRuns, only one FormattingRun will be applied on each replacement. FormattingRun that applied on the left part or right part of replacement will be either extended or shrunk.

    The other changes are for ticket #26 and my debugging purpose.

    Edit: the code is pasted on the 4th reply.

     

    Last edit: Merci chao 2014-06-07
  • Merci chao

    Merci chao - 2014-05-26

    The logic also works with HSSF.
    Here is the updated version.

    Edit: the code is pasted on the 4th reply.

     

    Last edit: Merci chao 2014-06-07
  • Randy Gettman

    Randy Gettman - 2014-06-02

    Format your entire expression with red text, not just 'foo'. JETT will assume that an entire expression is formatted with one formatting run, and the result of the evaluation will have the same formatting run. That's the only way that makes sense. I don't see any logical, consistent way to handle the manipulation of a formatting run when replacing an entire expression with its result, when the formatting run doesn't cover the entire expression. What should happen if the formatting run covers some code in the expression? What should happen if the formatting run covers the end of an expression and some text beyond the expression? I don't know, and JETT doesn't do anything special with these situations.

    Also, calling System.out.println in an expression will print to the system console, not return that value as the result of the expression.

    Even if you have

    ${java.lang.System.out.println('foo')} bar

    It will result in

     bar

    with "foo" being printed to the system console's standard out.

    Ensure that any formatting run in an expression covers the entire expression.

    If you try

    ${'foo'} bar

    then it will result in

    foo bar

     
  • Merci chao

    Merci chao - 2014-06-03

    I know it does not make sense to style part of an expression.
    I will never want to do it, but Excel always applies a default font for the unsupported characters.
    For example, if there is a cell, uses "Times New Roman" as the font, with the following value:

    ${null ? "A star ★" : "A triangle ▲"}

    You can see that "Times New Roman" won't be applied on the star and triangle symbol, because Times New Roman does not support these characters.

    Then IllegalArgumentException will be thrown during transformation.

    In my actual case, my users require the reports to use "Times New Roman" as the main font. However, those are Chinese reports, many Chinese strings are used in the expressions. Unfortunately, Times New Roman does not support Chinese characters thus mixed font style appears.

    Well, we can hard-code all unsupported characters inside Java code and pass them as variables for workaround. Obviously it is not a good idea for MVC pattern.
    Another available workaround is to use some complex solution to scrape the result, but it may make the code become unreadable and hard for maintaining:

    ${null ? "A star " : "A triangle "}${null ? "★" : "▲"}

    (use another font that supports these symbols for the second expression.)

    In my own opinion, JETT should handle these situations, and make a more comparatively reasonable result, rather than crashing with an ambiguous IllegalArgumentException.

    Like the rewritten version of RichTextStringUtil.updateFormattingRuns I have suggested, applying the first covering formatting to the whole replacement of expression is not a bad idea:

    /**
     * ......
     * @param targetLength The length of replacement target.
     */
    private static void updateFormattingRuns(List<FormattingRun> formattingRuns, int targetBegin,
            int change, int targetLength) {
        int targetEnd = targetBegin + targetLength;
    
        /*
         * apply the change for runs
         */
        for (Iterator<FormattingRun> itr = formattingRuns.iterator(); itr.hasNext();) {
            FormattingRun run = itr.next();
            int begin = run.getBegin();
            int length = run.getLength();
            int end = begin + length;
    
            /*
             * rearrange the begin and length of run if it covers part of target
             */
            if (begin <= targetBegin && targetBegin < end && end < targetEnd) {
                /*
                 * when the run covers the left part of the target,
                 * extend the run to make it cover the whole target
                 */
                length = length - end + targetEnd;
                end = targetEnd;
                run.setLength(length);
            } else if (targetBegin < begin && begin < targetEnd && targetEnd < end) {
                /*
                 * when the run covers the right part of the target and the following texts,
                 * shrink the run to make it not cover the target
                 */
                begin = targetEnd;
                length = end - targetEnd;
                run.setBegin(begin);
                run.setLength(length);
            } else if (targetBegin < begin && end <= targetEnd) {
                /*
                 * when the run only covers the middle or right part of target,
                 * remove it
                 */
                itr.remove();
                continue;
            }
    
            /*
             * update the length and begin of runs
             */
            if (begin <= targetBegin && targetBegin < end) {
                /*
                 * update the length of run if it covers the target
                 */
                length += change;
                if (length > 0)
                    /*
                     * update the length
                     */
                    run.setLength(length);
                else {
                    /*
                     * remove the zero-length run
                     */
                    itr.remove();
                    continue;
                }
            } else if (targetEnd <= begin) {
                /*
                 * shift the run to left if it covers the text behind the target
                 */
                begin += change;
                run.setBegin(begin);
            }
        }
    }
    

    P.S. This problem did make me crazy in finding out the root cause and writing patch for two whole working-days, ha...

     

    Last edit: Merci chao 2014-06-07
  • Merci chao

    Merci chao - 2014-06-07

    The suggested code has been updated in the previous reply.

     
  • Hêndi Marcos

    Hêndi Marcos - 2017-10-03

    Hi, was this resolved in version 0.10.0?

     
  • Hêndi Marcos

    Hêndi Marcos - 2017-10-07

    You're still wrong! Here is an annex project to simulate the error.
    Strange that occurs only when the result of the expression is less than the size of the expression.

            //Key with length <= 9 fail!
            int stringSize = 9;
    
            //Key with length > 10 work!
            //int stringSize = 10;
    
            Map<String, Object> beans = new HashMap<String,  Object>();
            beans.put("v", Arrays.asList(
                    new Entry(StringUtils.leftPad("X", stringSize), BigDecimal.valueOf(stringSize)), 
                    new Entry(StringUtils.leftPad("Y", stringSize), BigDecimal.valueOf(stringSize))));
    
     
  • Hêndi Marcos

    Hêndi Marcos - 2017-10-07

    I only managed to solve this if, but I do not know if it is the best way

    if (value.equals(target)) 
                return createFormattedString(0, helper, replacement, Collections.EMPTY_LIST);
    

    I did it on 0.11.0-SNAPSHOT

        public static RichTextString replaceAll(RichTextString richTextString,
                                                CreationHelper helper, String target, String replacement, boolean firstOnly, int startIdx,
                                                boolean identifierMode)
        {
            if (target == null || target.length() == 0)
                return richTextString;
    
            String value = richTextString.getString();
    
            if (value.equals(target)) 
                return createFormattedString(0, helper, replacement, Collections.EMPTY_LIST);
    
            int numFormattingRuns = richTextString.numFormattingRuns();
    
            logger.trace("replaceAll: \"{}\" ({}): numFormattingRuns={}, replacing \"{}\" with \"{}\".",
                    value, value.length(), numFormattingRuns, target, replacement);
    
     

    Last edit: Hêndi Marcos 2017-10-07

Log in to post a comment.