diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index be82f7f14..f401b0308 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 50258 - avoid corruption of XSSFWorkbook after applying XSSFRichTextRun#applyFont 50154 - Allow white spaces and unicode in OPC relationship targets 50113 - Remove cell from Calculation Chain after setting cell type to blank 49966 - Ensure that XSSFRow#removeCell cleares calculation chain entries diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRichTextString.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRichTextString.java index 91afa4980..d58da0e09 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRichTextString.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRichTextString.java @@ -17,7 +17,7 @@ package org.apache.poi.xssf.usermodel; -import java.util.ArrayList; +import java.util.*; import java.util.regex.Pattern; import java.util.regex.Matcher; @@ -132,7 +132,6 @@ public class XSSFRichTextString implements RichTextString { * @param endIndex The end index to apply to font to (exclusive) * @param font The index of the font to use. */ - @SuppressWarnings("deprecation") //YK: getXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support public void applyFont(int startIndex, int endIndex, Font font) { if (startIndex > endIndex) throw new IllegalArgumentException("Start index must be less than end index."); @@ -148,56 +147,15 @@ public class XSSFRichTextString implements RichTextString { } String text = getString(); - XSSFFont xssfFont = (XSSFFont)font; - ArrayList runs = new ArrayList(); - CTRElt[] r = st.getRArray(); - int pos = 0; - for (int i = 0; i < r.length; i++) { - int rStart = pos; - String t = r[i].getT(); - int rEnd = rStart + t.length(); + TreeMap formats = getFormatMap(st); + CTRPrElt fmt = CTRPrElt.Factory.newInstance(); + setRunAttributes(xssfFont.getCTFont(), fmt); + applyFont(formats, startIndex, endIndex, fmt); - if(rEnd <= startIndex) { - runs.add(r[i]); - pos += r[i].getT().length(); - } - else if (startIndex > rStart && startIndex < rEnd){ - CTRElt c = (CTRElt)r[i].copy(); - String txt = text.substring(rStart, startIndex); - c.setT(txt); - runs.add(c); - pos += txt.length(); - } else { - break; - } - } - CTRElt rt = CTRElt.Factory.newInstance(); - String txt = text.substring(startIndex, endIndex); - rt.setT(txt); - CTRPrElt pr = rt.addNewRPr(); - setRunAttributes(xssfFont.getCTFont(), pr); - runs.add(rt); - pos += txt.length(); - - for (int i = 0; i < r.length; i++) { - int rStart = pos; - String t = r[i].getT(); - int rEnd = Math.min(rStart + t.length(), text.length()); - - if (endIndex < rEnd){ - CTRElt c = (CTRElt)r[i].copy(); - txt = text.substring(rStart, rEnd); - c.setT(txt); - runs.add(c); - pos += txt.length(); - preserveSpaces(c.xgetT()); - } - } - - - st.setRArray(runs.toArray(new CTRElt[runs.size()])); + CTRst newSt = buildCTRst(text, formats); + st.set(newSt); } /** @@ -205,17 +163,8 @@ public class XSSFRichTextString implements RichTextString { * @param font The font to use. */ public void applyFont(Font font) { - if(st.sizeOfRArray() == 0 && st.isSetT()) { - CTRElt r = st.addNewR(); - r.setT(st.getT()); - setRunAttributes(((XSSFFont)font).getCTFont(), r.addNewRPr()); - st.unsetT(); - } else { - CTRElt r = CTRElt.Factory.newInstance(); - r.setT(getString()); - setRunAttributes(((XSSFFont)font).getCTFont(), r.addNewRPr()); - st.setRArray(new CTRElt[]{r}); - } + String text = getString(); + applyFont(0, text.length(), font); } /** @@ -231,7 +180,8 @@ public class XSSFRichTextString implements RichTextString { } else { font = styles.getFontAt(fontIndex); } - applyFont(font); + String text = getString(); + applyFont(0, text.length(), font); } /** @@ -295,9 +245,7 @@ public class XSSFRichTextString implements RichTextString { */ public void clearFormatting() { String text = getString(); - while (st.sizeOfRArray() > 0) { - st.removeR(st.sizeOfRArray()-1); - } + st.setRArray(null); st.setT(text); } @@ -531,4 +479,62 @@ public class XSSFRichTextString implements RichTextString { buf.append(value.substring(idx)); return buf.toString(); } + + void applyFont(TreeMap formats, int startIndex, int endIndex, CTRPrElt fmt) { + // delete format runs that fit between startIndex and endIndex + // runs intersecting startIndex and endIndex remain + int runStartIdx = 0; + for (Iterator it = formats.keySet().iterator(); it.hasNext();) { + int runEndIdx = it.next(); + if (runStartIdx >= startIndex && runEndIdx < endIndex) { + it.remove(); + } + runStartIdx = runEndIdx; + } + + if(startIndex > 0 && !formats.containsKey(startIndex)) { + Map.Entry he = formats.higherEntry(startIndex); //TODO TreeMap#higherEntry is JDK 1.6 only! + if(he != null) formats.put(startIndex, he.getValue()); + } + formats.put(endIndex, fmt); + + // assure that the range [startIndex, endIndex] consists if a single run + // there can be two or three runs depending whether startIndex or endIndex + // intersected existing format runs + SortedMap sub = formats.subMap(startIndex, endIndex); + while(sub.size() > 1) sub.remove(sub.lastKey()); + } + + TreeMap getFormatMap(CTRst entry){ + int length = 0; + TreeMap formats = new TreeMap(); + for (CTRElt r : entry.getRArray()) { + String txt = r.getT(); + CTRPrElt fmt = r.getRPr(); + + length += txt.length(); + formats.put(length, fmt); + } + return formats; + } + + CTRst buildCTRst(String text, TreeMap formats){ + if(text.length() != formats.lastKey()) { + throw new IllegalArgumentException("Text length was " + text.length() + + " but the last format index was " + formats.lastKey()); + } + CTRst st = CTRst.Factory.newInstance(); + int runStartIdx = 0; + for (Iterator it = formats.keySet().iterator(); it.hasNext();) { + int runEndIdx = it.next(); + CTRElt run = st.addNewR(); + String fragment = text.substring(runStartIdx, runEndIdx); + run.setT(fragment); + preserveSpaces(run.xgetT()); + CTRPrElt fmt = formats.get(runEndIdx); + if(fmt != null) run.setRPr(fmt); + runStartIdx = runEndIdx; + } + return st; + } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFRichTextString.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFRichTextString.java index 5251e06bb..e76966341 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFRichTextString.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFRichTextString.java @@ -21,6 +21,9 @@ import junit.framework.TestCase; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRst; import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXstring; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRPrElt; + +import java.util.TreeMap; /** * Tests functionality of the XSSFRichTextRun object @@ -53,23 +56,27 @@ public final class TestXSSFRichTextString extends TestCase { rt.append("4567"); rt.append("89"); + assertEquals("123456789", rt.getString()); + XSSFFont font1 = new XSSFFont(); font1.setBold(true); rt.applyFont(2, 5, font1); - assertEquals(5, rt.numFormattingRuns()); + assertEquals(4, rt.numFormattingRuns()); assertEquals(0, rt.getIndexOfFormattingRun(0)); - assertEquals(2, rt.getLengthOfFormattingRun(0)); + assertEquals("12", rt.getCTRst().getRArray(0).getT()); assertEquals(2, rt.getIndexOfFormattingRun(1)); - assertEquals(3, rt.getLengthOfFormattingRun(1)); + assertEquals("345", rt.getCTRst().getRArray(1).getT()); assertEquals(5, rt.getIndexOfFormattingRun(2)); - assertEquals(3, rt.getLengthOfFormattingRun(2)); + assertEquals(2, rt.getLengthOfFormattingRun(2)); + assertEquals("67", rt.getCTRst().getRArray(2).getT()); - assertEquals(8, rt.getIndexOfFormattingRun(3)); - assertEquals(1, rt.getLengthOfFormattingRun(3)); + assertEquals(7, rt.getIndexOfFormattingRun(3)); + assertEquals(2, rt.getLengthOfFormattingRun(3)); + assertEquals("89", rt.getCTRst().getRArray(3).getT()); } public void testClearFormatting() { @@ -142,4 +149,145 @@ public final class TestXSSFRichTextString extends TestCase { assertEquals("abc\r2ef\r", rt.getString()); } + + public void testApplyFont_lowlevel(){ + CTRst st = CTRst.Factory.newInstance(); + String text = "Apache Software Foundation"; + XSSFRichTextString str = new XSSFRichTextString(text); + assertEquals(26, text.length()); + + st.addNewR().setT(text); + + TreeMap formats = str.getFormatMap(st); + assertEquals(1, formats.size()); + assertEquals(26, (int)formats.firstEntry().getKey()); + assertNull(formats.firstEntry().getValue()); + + CTRPrElt fmt1 = CTRPrElt.Factory.newInstance(); + str.applyFont(formats, 0, 6, fmt1); + assertEquals(2, formats.size()); + assertEquals("[6, 26]", formats.keySet().toString()); + Object[] runs1 = formats.values().toArray(); + assertSame(fmt1, runs1[0]); + assertSame(null, runs1[1]); + + CTRPrElt fmt2 = CTRPrElt.Factory.newInstance(); + str.applyFont(formats, 7, 15, fmt2); + assertEquals(4, formats.size()); + assertEquals("[6, 7, 15, 26]", formats.keySet().toString()); + Object[] runs2 = formats.values().toArray(); + assertSame(fmt1, runs2[0]); + assertSame(null, runs2[1]); + assertSame(fmt2, runs2[2]); + assertSame(null, runs2[3]); + + CTRPrElt fmt3 = CTRPrElt.Factory.newInstance(); + str.applyFont(formats, 6, 7, fmt3); + assertEquals(4, formats.size()); + assertEquals("[6, 7, 15, 26]", formats.keySet().toString()); + Object[] runs3 = formats.values().toArray(); + assertSame(fmt1, runs3[0]); + assertSame(fmt3, runs3[1]); + assertSame(fmt2, runs3[2]); + assertSame(null, runs3[3]); + + CTRPrElt fmt4 = CTRPrElt.Factory.newInstance(); + str.applyFont(formats, 0, 7, fmt4); + assertEquals(3, formats.size()); + assertEquals("[7, 15, 26]", formats.keySet().toString()); + Object[] runs4 = formats.values().toArray(); + assertSame(fmt4, runs4[0]); + assertSame(fmt2, runs4[1]); + assertSame(null, runs4[2]); + + CTRPrElt fmt5 = CTRPrElt.Factory.newInstance(); + str.applyFont(formats, 0, 26, fmt5); + assertEquals(1, formats.size()); + assertEquals("[26]", formats.keySet().toString()); + Object[] runs5 = formats.values().toArray(); + assertSame(fmt5, runs5[0]); + + CTRPrElt fmt6 = CTRPrElt.Factory.newInstance(); + str.applyFont(formats, 15, 26, fmt6); + assertEquals(2, formats.size()); + assertEquals("[15, 26]", formats.keySet().toString()); + Object[] runs6 = formats.values().toArray(); + assertSame(fmt5, runs6[0]); + assertSame(fmt6, runs6[1]); + + str.applyFont(formats, 0, 26, null); + assertEquals(1, formats.size()); + assertEquals("[26]", formats.keySet().toString()); + Object[] runs7 = formats.values().toArray(); + assertSame(null, runs7[0]); + + str.applyFont(formats, 15, 26, fmt6); + assertEquals(2, formats.size()); + assertEquals("[15, 26]", formats.keySet().toString()); + Object[] runs8 = formats.values().toArray(); + assertSame(null, runs8[0]); + assertSame(fmt6, runs8[1]); + + str.applyFont(formats, 15, 26, fmt5); + assertEquals(2, formats.size()); + assertEquals("[15, 26]", formats.keySet().toString()); + Object[] runs9 = formats.values().toArray(); + assertSame(null, runs9[0]); + assertSame(fmt5, runs9[1]); + + str.applyFont(formats, 2, 20, fmt6); + assertEquals(3, formats.size()); + assertEquals("[2, 20, 26]", formats.keySet().toString()); + Object[] runs10 = formats.values().toArray(); + assertSame(null, runs10[0]); + assertSame(fmt6, runs10[1]); + assertSame(fmt5, runs10[2]); + + str.applyFont(formats, 22, 24, fmt4); + assertEquals(5, formats.size()); + assertEquals("[2, 20, 22, 24, 26]", formats.keySet().toString()); + Object[] runs11 = formats.values().toArray(); + assertSame(null, runs11[0]); + assertSame(fmt6, runs11[1]); + assertSame(fmt5, runs11[2]); + assertSame(fmt4, runs11[3]); + assertSame(fmt5, runs11[4]); + + str.applyFont(formats, 0, 10, fmt1); + assertEquals(5, formats.size()); + assertEquals("[10, 20, 22, 24, 26]", formats.keySet().toString()); + Object[] runs12 = formats.values().toArray(); + assertSame(fmt1, runs12[0]); + assertSame(fmt6, runs12[1]); + assertSame(fmt5, runs12[2]); + assertSame(fmt4, runs12[3]); + assertSame(fmt5, runs12[4]); + } + + public void testApplyFont_usermodel(){ + String text = "Apache Software Foundation"; + XSSFRichTextString str = new XSSFRichTextString(text); + XSSFFont font1 = new XSSFFont(); + XSSFFont font2 = new XSSFFont(); + XSSFFont font3 = new XSSFFont(); + str.applyFont(font1); + assertEquals(1, str.numFormattingRuns()); + + str.applyFont(0, 6, font1); + str.applyFont(6, text.length(), font2); + assertEquals(2, str.numFormattingRuns()); + assertEquals("Apache", str.getCTRst().getRArray(0).getT()); + assertEquals(" Software Foundation", str.getCTRst().getRArray(1).getT()); + + str.applyFont(15, 26, font3); + assertEquals(3, str.numFormattingRuns()); + assertEquals("Apache", str.getCTRst().getRArray(0).getT()); + assertEquals(" Software", str.getCTRst().getRArray(1).getT()); + assertEquals(" Foundation", str.getCTRst().getRArray(2).getT()); + + str.applyFont(6, text.length(), font2); + assertEquals(2, str.numFormattingRuns()); + assertEquals("Apache", str.getCTRst().getRArray(0).getT()); + assertEquals(" Software Foundation", str.getCTRst().getRArray(1).getT()); + } }