[{"data":1,"prerenderedAt":2128},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter":2093,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter":2102,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter":2107,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter":2108},{"id":4,"title":5,"body":6,"dateModified":2059,"datePublished":2059,"description":2060,"extension":2061,"faq":2062,"keyword":2071,"kind":2072,"meta":2073,"navigation":210,"path":2082,"seo":2083,"slug":2086,"stem":2087,"topics":2088,"type":2091,"__hash__":2092},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter\u002Findex.md","Write Rich Text with Mixed Formats in One Cell with xlsxwriter",{"type":7,"value":8,"toc":2046},"minimark",[9,29,143,148,179,183,188,347,354,358,365,528,535,759,763,768,848,859,1087,1094,1098,1101,1441,1448,1452,1459,1603,1621,1737,1741,1751,1895,1919,1923,1955,1959,1981,1985,1991,1997,2003,2009,2013,2042],[10,11,12,13,17,18,22,23,28],"p",{},"Most cells hold one style: a bold header, a red number. Some content reads better with mixed formatting inside the cell — a status line such as \"Status: ",[14,15,16],"strong",{},"overdue"," since 3 March\" with the key word in red, a unit with a superscript like m³, or a note whose first word is bold. Excel calls this rich text, and xlsxwriter writes it with ",[19,20,21],"code",{},"write_rich_string",". This guide, part of ",[24,25,27],"a",{"href":26},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002F","Building Excel Reports with xlsxwriter",", covers the argument pattern, the quiet ways a rich string can fail, and how to keep the formatting when another tool edits the file afterwards.",[30,31,39,40,39,44,39,48,39,56,39,65,39,69,39,76,39,80,39,85,39,87,39,91,39,94,39,98,39,103,39,107,39,110,39,114,39,118,39,122,39,125,39,129,39,133,39,137],"svg",{"viewBox":32,"role":33,"ariaLabelledBy":34,"xmlns":37,"style":38},"157 0 445 194","img",[35,36],"rt-sheet-t","rt-sheet-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:445px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[41,42,43],"title",{"id":35},"Cells with mixed formatting",[45,46,47],"desc",{"id":36},"Cell A1 reads Status overdue since 3 March with the word overdue in bold red. Cell A2 shows CO2 emissions in m3 with the 2 as a subscript and the 3 as a superscript. Cell A3 has a bold Note label followed by a second line of plain text in a wrapped cell.",[49,50],"rect",{"x":51,"y":52,"width":53,"height":54,"fill":55},"157","0","445","194","#ffffff",[49,57],{"x":58,"y":59,"width":60,"height":61,"rx":52,"fill":62,"stroke":63,"style":64},"249.0","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[49,66],{"x":67,"y":59,"width":68,"height":61,"rx":52,"fill":62,"stroke":63,"style":64},"281.0","230.0",[70,71,75],"text",{"x":72,"y":73,"style":74},"396.0","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[49,77],{"x":58,"y":78,"width":60,"height":79,"rx":52,"fill":62,"stroke":63,"style":64},"40.0","28.0",[70,81,84],{"x":82,"y":83,"style":74},"265.0","59.0","1",[49,86],{"x":67,"y":78,"width":68,"height":79,"rx":52,"fill":55,"stroke":63,"style":64},[70,88,90],{"x":72,"y":83,"style":89},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Rich text in one cell",[49,92],{"x":58,"y":93,"width":60,"height":79,"rx":52,"fill":62,"stroke":63,"style":64},"68.0",[70,95,97],{"x":82,"y":96,"style":74},"87.0","2",[49,99],{"x":67,"y":93,"width":68,"height":79,"rx":52,"fill":100,"stroke":101,"style":102},"#fee8f2","var(--accent,#f43f8f)","stroke-width:1.5px",[70,104,106],{"x":72,"y":96,"style":105},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Status: OVERDUE since 3 March",[49,108],{"x":58,"y":109,"width":60,"height":79,"rx":52,"fill":62,"stroke":63,"style":64},"96.0",[70,111,113],{"x":82,"y":112,"style":74},"115.0","3",[49,115],{"x":67,"y":109,"width":68,"height":79,"rx":52,"fill":116,"stroke":117,"style":102},"#d9f4f1","var(--teal,#0f9488)",[70,119,121],{"x":72,"y":112,"style":120},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","CO₂ emissions in m³",[49,123],{"x":58,"y":124,"width":60,"height":79,"rx":52,"fill":62,"stroke":63,"style":64},"124.0",[70,126,128],{"x":82,"y":127,"style":74},"143.0","4",[49,130],{"x":67,"y":124,"width":68,"height":79,"rx":52,"fill":131,"stroke":132,"style":102},"#f0f4ff","var(--brand,#5b5cf0)",[70,134,136],{"x":72,"y":127,"style":135},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Note: ↵ check the March accruals",[70,138,142],{"x":139,"y":140,"style":141},"380.0","174.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","each cell holds one string made of differently formatted fragments",[144,145,147],"h2",{"id":146},"prerequisites","Prerequisites",[149,150,155],"pre",{"className":151,"code":152,"language":153,"meta":154,"style":154},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install xlsxwriter openpyxl pandas\n","bash","",[19,156,157],{"__ignoreMap":154},[158,159,162,166,170,173,176],"span",{"class":160,"line":161},"line",1,[158,163,165],{"class":164},"sMTad","pip",[158,167,169],{"class":168},"srMev"," install",[158,171,172],{"class":168}," xlsxwriter",[158,174,175],{"class":168}," openpyxl",[158,177,178],{"class":168}," pandas\n",[144,180,182],{"id":181},"step-1-fragments-and-formats","Step 1 — Fragments and formats",[10,184,185,187],{},[19,186,21],{}," takes the cell, then a sequence of strings and formats. A format applies to the string immediately after it; a string with no format before it uses the cell's default font:",[149,189,193],{"className":190,"code":191,"language":192,"meta":154,"style":154},"language-python shiki shiki-themes github-light github-dark-high-contrast","import xlsxwriter\n\nwb = xlsxwriter.Workbook(\"rich.xlsx\")\nws = wb.add_worksheet(\"Notes\")\nws.set_column(\"A:A\", 42)\n\nred_bold = wb.add_format({\"bold\": True, \"font_color\": \"#B91C1C\"})\nresult = ws.write_rich_string(\"A1\", \"Status: \", red_bold, \"overdue\", \" since 3 March\")\nprint(result)                                # 0 means written\n","python",[19,194,195,205,212,230,246,264,269,302,334],{"__ignoreMap":154},[158,196,197,201],{"class":160,"line":161},[158,198,200],{"class":199},"s-kum","import",[158,202,204],{"class":203},"skGVy"," xlsxwriter\n",[158,206,208],{"class":160,"line":207},2,[158,209,211],{"emptyLinePlaceholder":210},true,"\n",[158,213,215,218,221,224,227],{"class":160,"line":214},3,[158,216,217],{"class":203},"wb ",[158,219,220],{"class":199},"=",[158,222,223],{"class":203}," xlsxwriter.Workbook(",[158,225,226],{"class":168},"\"rich.xlsx\"",[158,228,229],{"class":203},")\n",[158,231,233,236,238,241,244],{"class":160,"line":232},4,[158,234,235],{"class":203},"ws ",[158,237,220],{"class":199},[158,239,240],{"class":203}," wb.add_worksheet(",[158,242,243],{"class":168},"\"Notes\"",[158,245,229],{"class":203},[158,247,249,252,255,258,262],{"class":160,"line":248},5,[158,250,251],{"class":203},"ws.set_column(",[158,253,254],{"class":168},"\"A:A\"",[158,256,257],{"class":203},", ",[158,259,261],{"class":260},"sP0c6","42",[158,263,229],{"class":203},[158,265,267],{"class":160,"line":266},6,[158,268,211],{"emptyLinePlaceholder":210},[158,270,272,275,277,280,283,286,289,291,294,296,299],{"class":160,"line":271},7,[158,273,274],{"class":203},"red_bold ",[158,276,220],{"class":199},[158,278,279],{"class":203}," wb.add_format({",[158,281,282],{"class":168},"\"bold\"",[158,284,285],{"class":203},": ",[158,287,288],{"class":260},"True",[158,290,257],{"class":203},[158,292,293],{"class":168},"\"font_color\"",[158,295,285],{"class":203},[158,297,298],{"class":168},"\"#B91C1C\"",[158,300,301],{"class":203},"})\n",[158,303,305,308,310,313,316,318,321,324,327,329,332],{"class":160,"line":304},8,[158,306,307],{"class":203},"result ",[158,309,220],{"class":199},[158,311,312],{"class":203}," ws.write_rich_string(",[158,314,315],{"class":168},"\"A1\"",[158,317,257],{"class":203},[158,319,320],{"class":168},"\"Status: \"",[158,322,323],{"class":203},", red_bold, ",[158,325,326],{"class":168},"\"overdue\"",[158,328,257],{"class":203},[158,330,331],{"class":168},"\" since 3 March\"",[158,333,229],{"class":203},[158,335,337,340,343],{"class":160,"line":336},9,[158,338,339],{"class":260},"print",[158,341,342],{"class":203},"(result)                                ",[158,344,346],{"class":345},"s-wDw","# 0 means written\n",[10,348,349,350,353],{},"Read the arguments in pairs from the left: \"Status: \" is plain, ",[19,351,352],{},"red_bold"," applies to \"overdue\", and \" since 3 March\" is plain again. Spaces belong inside the fragments — there is no automatic separator, so \"Status:\" followed by \"overdue\" without a space would read \"Status:overdue\". The whole cell is still one text value; formulas that refer to it see \"Status: overdue since 3 March\".",[144,355,357],{"id":356},"step-2-superscripts-subscripts-and-line-breaks","Step 2 — Superscripts, subscripts and line breaks",[10,359,360,361,364],{},"The same pattern handles scientific and unit notation with ",[19,362,363],{},"font_script"," — 1 for superscript, 2 for subscript — and multi-line notes with a newline plus a wrapped cell format as the final argument:",[149,366,368],{"className":190,"code":367,"language":192,"meta":154,"style":154},"sub = wb.add_format({\"font_script\": 2})\nsup = wb.add_format({\"font_script\": 1})\nbold = wb.add_format({\"bold\": True})\nwrap = wb.add_format({\"text_wrap\": True, \"valign\": \"top\"})\n\nws.write_rich_string(\"A2\", \"CO\", sub, \"2\", \" emissions in m\", sup, \"3\")\nws.write_rich_string(\"A3\", bold, \"Note:\", \"\\ncheck the March accruals\", wrap)\nws.set_row(2, 32)\n",[19,369,370,388,405,422,450,454,486,514],{"__ignoreMap":154},[158,371,372,375,377,379,382,384,386],{"class":160,"line":161},[158,373,374],{"class":203},"sub ",[158,376,220],{"class":199},[158,378,279],{"class":203},[158,380,381],{"class":168},"\"font_script\"",[158,383,285],{"class":203},[158,385,97],{"class":260},[158,387,301],{"class":203},[158,389,390,393,395,397,399,401,403],{"class":160,"line":207},[158,391,392],{"class":203},"sup ",[158,394,220],{"class":199},[158,396,279],{"class":203},[158,398,381],{"class":168},[158,400,285],{"class":203},[158,402,84],{"class":260},[158,404,301],{"class":203},[158,406,407,410,412,414,416,418,420],{"class":160,"line":214},[158,408,409],{"class":203},"bold ",[158,411,220],{"class":199},[158,413,279],{"class":203},[158,415,282],{"class":168},[158,417,285],{"class":203},[158,419,288],{"class":260},[158,421,301],{"class":203},[158,423,424,427,429,431,434,436,438,440,443,445,448],{"class":160,"line":232},[158,425,426],{"class":203},"wrap ",[158,428,220],{"class":199},[158,430,279],{"class":203},[158,432,433],{"class":168},"\"text_wrap\"",[158,435,285],{"class":203},[158,437,288],{"class":260},[158,439,257],{"class":203},[158,441,442],{"class":168},"\"valign\"",[158,444,285],{"class":203},[158,446,447],{"class":168},"\"top\"",[158,449,301],{"class":203},[158,451,452],{"class":160,"line":248},[158,453,211],{"emptyLinePlaceholder":210},[158,455,456,459,462,464,467,470,473,475,478,481,484],{"class":160,"line":266},[158,457,458],{"class":203},"ws.write_rich_string(",[158,460,461],{"class":168},"\"A2\"",[158,463,257],{"class":203},[158,465,466],{"class":168},"\"CO\"",[158,468,469],{"class":203},", sub, ",[158,471,472],{"class":168},"\"2\"",[158,474,257],{"class":203},[158,476,477],{"class":168},"\" emissions in m\"",[158,479,480],{"class":203},", sup, ",[158,482,483],{"class":168},"\"3\"",[158,485,229],{"class":203},[158,487,488,490,493,496,499,501,504,508,511],{"class":160,"line":271},[158,489,458],{"class":203},[158,491,492],{"class":168},"\"A3\"",[158,494,495],{"class":203},", bold, ",[158,497,498],{"class":168},"\"Note:\"",[158,500,257],{"class":203},[158,502,503],{"class":168},"\"",[158,505,507],{"class":506},"sSjpA","\\n",[158,509,510],{"class":168},"check the March accruals\"",[158,512,513],{"class":203},", wrap)\n",[158,515,516,519,521,523,526],{"class":160,"line":304},[158,517,518],{"class":203},"ws.set_row(",[158,520,97],{"class":260},[158,522,257],{"class":203},[158,524,525],{"class":260},"32",[158,527,229],{"class":203},[10,529,530,531,534],{},"A format passed after the last fragment applies to the cell as a whole — alignment, wrapping, borders, fill — rather than to a fragment. Without ",[19,532,533],{},"text_wrap",", Excel shows the newline as nothing and runs the lines together. Setting the row height avoids relying on Excel to auto-fit a wrapped row, which it does not always do for files it did not create.",[30,536,39,542,39,545,39,548,39,552,39,559,39,564,39,581,39,583,39,592,39,595,39,606,39,609,39,612,39,614,39,622,39,625,39,629,39,631,39,646,39,650,39,653,39,655,39,663,39,667,39,671,39,673,39,683,39,689,39,692,39,695,39,699,39,702,39,705,39,707,39,712,39,716,39,719,39,722,39,724,39,728,39,731,39,734,39,737,39,740,39,744,39,747,39,749,39,751,39,753,39,756],{"viewBox":537,"role":33,"ariaLabelledBy":538,"xmlns":37,"style":541},"4 0 752 310",[539,540],"rt-args-t","rt-args-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[41,543,544],{"id":539},"Reading the arguments of write_rich_string",[45,546,547],{"id":540},"The call write_rich_string A1, Status, red_bold, overdue, since 3 March has a cell reference, a plain fragment, a format, the fragment it applies to, and a final plain fragment. An optional last format applies to the whole cell.",[49,549],{"x":128,"y":52,"width":550,"height":551,"fill":55},"752","310",[49,553],{"x":554,"y":59,"width":555,"height":556,"rx":557,"fill":558,"stroke":63,"style":102},"20.0","720.0","150.0","12","#f0f2f5",[70,560,84],{"x":561,"y":562,"style":563},"60.0","47.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",[70,565,568,573,576,578],{"x":566,"y":562,"style":567},"82.0","font-size:12.5px;text-anchor:start;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",[569,570,572],"tspan",{"style":571},"fill:var(--text,#172033);font-weight:400","ws",[569,574,575],{"style":571},".",[569,577,21],{"style":571},[569,579,580],{"style":571},"(",[70,582,97],{"x":561,"y":93,"style":563},[584,585],"circle",{"cx":586,"cy":587,"r":588,"fill":589,"stroke":590,"style":591},"36.0","63.5","11.0","#4338ca","none","stroke-width:0px",[70,593,84],{"x":586,"y":93,"style":594},"font-size:12px;font-weight:700;fill:#ffffff;text-anchor:middle",[70,596,597,600,603],{"x":566,"y":93,"style":567},[569,598,599],{"style":571},"    ",[569,601,315],{"style":602},"fill:var(--teal-ink,#0b6157);font-weight:400",[569,604,605],{"style":571},",",[70,607,113],{"x":561,"y":608,"style":563},"89.0",[584,610],{"cx":586,"cy":611,"r":588,"fill":589,"stroke":590,"style":591},"84.5",[70,613,97],{"x":586,"y":608,"style":594},[70,615,616,618,620],{"x":566,"y":608,"style":567},[569,617,599],{"style":571},[569,619,320],{"style":602},[569,621,605],{"style":571},[70,623,128],{"x":561,"y":624,"style":563},"110.0",[584,626],{"cx":586,"cy":627,"r":588,"fill":628,"stroke":590,"style":591},"105.5","#be185d",[70,630,113],{"x":586,"y":624,"style":594},[70,632,633,635,637,639,642,644],{"x":566,"y":624,"style":567},[569,634,599],{"style":571},[569,636,352],{"style":571},[569,638,605],{"style":571},[569,640,641],{"style":571}," ",[569,643,326],{"style":602},[569,645,605],{"style":571},[70,647,649],{"x":561,"y":648,"style":563},"131.0","5",[584,651],{"cx":586,"cy":652,"r":588,"fill":589,"stroke":590,"style":591},"126.5",[70,654,128],{"x":586,"y":648,"style":594},[70,656,657,659,661],{"x":566,"y":648,"style":567},[569,658,599],{"style":571},[569,660,331],{"style":602},[569,662,605],{"style":571},[70,664,666],{"x":561,"y":665,"style":563},"152.0","6",[584,668],{"cx":586,"cy":669,"r":588,"fill":670,"stroke":590,"style":591},"147.5","#0b6157",[70,672,649],{"x":586,"y":665,"style":594},[70,674,675,677,680],{"x":566,"y":665,"style":567},[569,676,599],{"style":571},[569,678,679],{"style":571},"wrap",[569,681,682],{"style":571},")",[49,684],{"x":554,"y":685,"width":686,"height":687,"rx":688,"fill":131,"stroke":132,"style":102},"180.0","230.7","52.0","10",[584,690],{"cx":78,"cy":691,"r":588,"fill":589,"stroke":590,"style":591},"206.0",[70,693,84],{"x":78,"y":694,"style":594},"210.5",[70,696,698],{"x":561,"y":694,"style":697},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","the cell",[49,700],{"x":701,"y":685,"width":686,"height":687,"rx":688,"fill":131,"stroke":132,"style":102},"264.7",[584,703],{"cx":704,"cy":691,"r":588,"fill":589,"stroke":590,"style":591},"284.7",[70,706,97],{"x":704,"y":694,"style":594},[70,708,711],{"x":709,"y":710,"style":697},"304.7","202.0","plain fragment in the default",[70,713,715],{"x":709,"y":714,"style":697},"219.0","font",[49,717],{"x":718,"y":685,"width":686,"height":687,"rx":688,"fill":100,"stroke":101,"style":102},"509.3",[584,720],{"cx":721,"cy":691,"r":588,"fill":628,"stroke":590,"style":591},"529.3",[70,723,113],{"x":721,"y":694,"style":594},[70,725,727],{"x":726,"y":710,"style":697},"549.3","a format, then the fragment it",[70,729,730],{"x":726,"y":714,"style":697},"styles",[49,732],{"x":554,"y":733,"width":686,"height":687,"rx":688,"fill":131,"stroke":132,"style":102},"242.0",[584,735],{"cx":78,"cy":736,"r":588,"fill":589,"stroke":590,"style":591},"268.0",[70,738,128],{"x":78,"y":739,"style":594},"272.5",[70,741,743],{"x":561,"y":742,"style":697},"264.0","plain again; spaces belong to",[70,745,746],{"x":561,"y":67,"style":697},"the text",[49,748],{"x":701,"y":733,"width":686,"height":687,"rx":688,"fill":116,"stroke":117,"style":102},[584,750],{"cx":704,"cy":736,"r":588,"fill":670,"stroke":590,"style":591},[70,752,649],{"x":704,"y":739,"style":594},[70,754,755],{"x":709,"y":742,"style":697},"optional last format for the",[70,757,758],{"x":709,"y":67,"style":697},"whole cell",[144,760,762],{"id":761},"step-3-check-the-return-code","Step 3 — Check the return code",[10,764,765,767],{},[19,766,21],{}," does not raise when it cannot write; it emits a warning, returns a negative number and leaves the cell empty. Two cases catch people out:",[149,769,771],{"className":190,"code":770,"language":192,"meta":154,"style":154},"print(ws.write_rich_string(\"A5\", bold, \"only one fragment\"))     # -5\nprint(ws.write_rich_string(\"A6\", \"plain\", \"text\"))               # -5\nprint(ws.write_rich_string(\"A7\", \"a\", bold, \"\", \"c\"))            # -4\n",[19,772,773,794,818],{"__ignoreMap":154},[158,774,775,777,780,783,785,788,791],{"class":160,"line":161},[158,776,339],{"class":260},[158,778,779],{"class":203},"(ws.write_rich_string(",[158,781,782],{"class":168},"\"A5\"",[158,784,495],{"class":203},[158,786,787],{"class":168},"\"only one fragment\"",[158,789,790],{"class":203},"))     ",[158,792,793],{"class":345},"# -5\n",[158,795,796,798,800,803,805,808,810,813,816],{"class":160,"line":207},[158,797,339],{"class":260},[158,799,779],{"class":203},[158,801,802],{"class":168},"\"A6\"",[158,804,257],{"class":203},[158,806,807],{"class":168},"\"plain\"",[158,809,257],{"class":203},[158,811,812],{"class":168},"\"text\"",[158,814,815],{"class":203},"))               ",[158,817,793],{"class":345},[158,819,820,822,824,827,829,832,834,837,839,842,845],{"class":160,"line":214},[158,821,339],{"class":260},[158,823,779],{"class":203},[158,825,826],{"class":168},"\"A7\"",[158,828,257],{"class":203},[158,830,831],{"class":168},"\"a\"",[158,833,495],{"class":203},[158,835,836],{"class":168},"\"\"",[158,838,257],{"class":203},[158,840,841],{"class":168},"\"c\"",[158,843,844],{"class":203},"))            ",[158,846,847],{"class":345},"# -4\n",[10,849,850,851,854,855,858],{},"A rich string needs at least three items — two fragments with a format between them, or a format and fragment plus another fragment. A single formatted fragment is not rich text: write it with ",[19,852,853],{},"ws.write(\"A5\", \"only one fragment\", bold)"," instead. And Excel does not allow empty fragments, which easily appear when text is built from data — a status that happens to be blank. Build the arguments in a helper that drops empty pieces and falls back to ",[19,856,857],{},"write"," when fewer than three remain:",[149,860,862],{"className":190,"code":861,"language":192,"meta":154,"style":154},"def write_rich(ws, cell, parts, cell_format=None):\n    args = []\n    for fmt, text in parts:\n        if text:\n            args += [fmt, text] if fmt else [text]\n    if len(args) \u003C 3:\n        plain = \"\".join(a for a in args if isinstance(a, str))\n        return ws.write(cell, plain, cell_format)\n    return ws.write_rich_string(cell, *args, *([cell_format] if cell_format else []))\n\nprint(write_rich(ws, \"A8\", [(None, \"Owner: \"), (bold, \"\"), (None, \"unassigned\")]))   # 0\nwb.close()\n",[19,863,864,884,894,908,916,939,959,997,1005,1034,1039,1081],{"__ignoreMap":154},[158,865,866,869,873,876,878,881],{"class":160,"line":161},[158,867,868],{"class":199},"def",[158,870,872],{"class":871},"s_Opv"," write_rich",[158,874,875],{"class":203},"(ws, cell, parts, cell_format",[158,877,220],{"class":199},[158,879,880],{"class":260},"None",[158,882,883],{"class":203},"):\n",[158,885,886,889,891],{"class":160,"line":207},[158,887,888],{"class":203},"    args ",[158,890,220],{"class":199},[158,892,893],{"class":203}," []\n",[158,895,896,899,902,905],{"class":160,"line":214},[158,897,898],{"class":199},"    for",[158,900,901],{"class":203}," fmt, text ",[158,903,904],{"class":199},"in",[158,906,907],{"class":203}," parts:\n",[158,909,910,913],{"class":160,"line":232},[158,911,912],{"class":199},"        if",[158,914,915],{"class":203}," text:\n",[158,917,918,921,924,927,930,933,936],{"class":160,"line":248},[158,919,920],{"class":203},"            args ",[158,922,923],{"class":199},"+=",[158,925,926],{"class":203}," [fmt, text] ",[158,928,929],{"class":199},"if",[158,931,932],{"class":203}," fmt ",[158,934,935],{"class":199},"else",[158,937,938],{"class":203}," [text]\n",[158,940,941,944,947,950,953,956],{"class":160,"line":266},[158,942,943],{"class":199},"    if",[158,945,946],{"class":260}," len",[158,948,949],{"class":203},"(args) ",[158,951,952],{"class":199},"\u003C",[158,954,955],{"class":260}," 3",[158,957,958],{"class":203},":\n",[158,960,961,964,966,969,972,975,978,980,983,985,988,991,994],{"class":160,"line":271},[158,962,963],{"class":203},"        plain ",[158,965,220],{"class":199},[158,967,968],{"class":168}," \"\"",[158,970,971],{"class":203},".join(a ",[158,973,974],{"class":199},"for",[158,976,977],{"class":203}," a ",[158,979,904],{"class":199},[158,981,982],{"class":203}," args ",[158,984,929],{"class":199},[158,986,987],{"class":260}," isinstance",[158,989,990],{"class":203},"(a, ",[158,992,993],{"class":260},"str",[158,995,996],{"class":203},"))\n",[158,998,999,1002],{"class":160,"line":304},[158,1000,1001],{"class":199},"        return",[158,1003,1004],{"class":203}," ws.write(cell, plain, cell_format)\n",[158,1006,1007,1010,1013,1016,1019,1021,1024,1026,1029,1031],{"class":160,"line":336},[158,1008,1009],{"class":199},"    return",[158,1011,1012],{"class":203}," ws.write_rich_string(cell, ",[158,1014,1015],{"class":199},"*",[158,1017,1018],{"class":203},"args, ",[158,1020,1015],{"class":199},[158,1022,1023],{"class":203},"([cell_format] ",[158,1025,929],{"class":199},[158,1027,1028],{"class":203}," cell_format ",[158,1030,935],{"class":199},[158,1032,1033],{"class":203}," []))\n",[158,1035,1037],{"class":160,"line":1036},10,[158,1038,211],{"emptyLinePlaceholder":210},[158,1040,1042,1044,1047,1050,1053,1055,1057,1060,1063,1065,1068,1070,1072,1075,1078],{"class":160,"line":1041},11,[158,1043,339],{"class":260},[158,1045,1046],{"class":203},"(write_rich(ws, ",[158,1048,1049],{"class":168},"\"A8\"",[158,1051,1052],{"class":203},", [(",[158,1054,880],{"class":260},[158,1056,257],{"class":203},[158,1058,1059],{"class":168},"\"Owner: \"",[158,1061,1062],{"class":203},"), (bold, ",[158,1064,836],{"class":168},[158,1066,1067],{"class":203},"), (",[158,1069,880],{"class":260},[158,1071,257],{"class":203},[158,1073,1074],{"class":168},"\"unassigned\"",[158,1076,1077],{"class":203},")]))   ",[158,1079,1080],{"class":345},"# 0\n",[158,1082,1084],{"class":160,"line":1083},12,[158,1085,1086],{"class":203},"wb.close()\n",[10,1088,1089,1090,1093],{},"Checking the result in a report generator — or turning warnings into errors with ",[19,1091,1092],{},"warnings.simplefilter(\"error\")"," in tests — stops blank cells from slipping into a report unnoticed.",[144,1095,1097],{"id":1096},"step-4-rich-text-in-a-pandas-report","Step 4 — Rich text in a pandas report",[10,1099,1100],{},"pandas writes plain values. Write the frame first, then overwrite the cells that need mixed formatting using the same XlsxWriter worksheet:",[149,1102,1104],{"className":190,"code":1103,"language":192,"meta":154,"style":154},"import pandas as pd\n\ntasks = pd.DataFrame({\"task\": [\"Renew licence\", \"Quarter close\", \"Audit prep\"],\n                      \"status\": [\"overdue\", \"on track\", \"overdue\"], \"days\": [10, 0, 3]})\nwith pd.ExcelWriter(\"tasks.xlsx\", engine=\"xlsxwriter\") as writer:\n    tasks.to_excel(writer, sheet_name=\"Tasks\", index=False)\n    book, sheet = writer.book, writer.sheets[\"Tasks\"]\n    alert = book.add_format({\"bold\": True, \"font_color\": \"#B91C1C\"})\n    calm = book.add_format({\"font_color\": \"#0B6157\"})\n    for i, row in tasks.iterrows():\n        fmt = alert if row[\"status\"] == \"overdue\" else calm\n        sheet.write_rich_string(i + 1, 1, fmt, row[\"status\"], f\" ({row['days']} days)\")\n    sheet.set_column(\"A:B\", 20)\nprint(pd.read_excel(\"tasks.xlsx\")[\"status\"].tolist())\n# ['overdue (10 days)', 'on track (0 days)', 'overdue (3 days)']\n",[19,1105,1106,1119,1123,1155,1194,1224,1249,1264,1290,1308,1320,1353,1401,1417,1435],{"__ignoreMap":154},[158,1107,1108,1110,1113,1116],{"class":160,"line":161},[158,1109,200],{"class":199},[158,1111,1112],{"class":203}," pandas ",[158,1114,1115],{"class":199},"as",[158,1117,1118],{"class":203}," pd\n",[158,1120,1121],{"class":160,"line":207},[158,1122,211],{"emptyLinePlaceholder":210},[158,1124,1125,1128,1130,1133,1136,1139,1142,1144,1147,1149,1152],{"class":160,"line":214},[158,1126,1127],{"class":203},"tasks ",[158,1129,220],{"class":199},[158,1131,1132],{"class":203}," pd.DataFrame({",[158,1134,1135],{"class":168},"\"task\"",[158,1137,1138],{"class":203},": [",[158,1140,1141],{"class":168},"\"Renew licence\"",[158,1143,257],{"class":203},[158,1145,1146],{"class":168},"\"Quarter close\"",[158,1148,257],{"class":203},[158,1150,1151],{"class":168},"\"Audit prep\"",[158,1153,1154],{"class":203},"],\n",[158,1156,1157,1160,1162,1164,1166,1169,1171,1173,1176,1179,1181,1183,1185,1187,1189,1191],{"class":160,"line":232},[158,1158,1159],{"class":168},"                      \"status\"",[158,1161,1138],{"class":203},[158,1163,326],{"class":168},[158,1165,257],{"class":203},[158,1167,1168],{"class":168},"\"on track\"",[158,1170,257],{"class":203},[158,1172,326],{"class":168},[158,1174,1175],{"class":203},"], ",[158,1177,1178],{"class":168},"\"days\"",[158,1180,1138],{"class":203},[158,1182,688],{"class":260},[158,1184,257],{"class":203},[158,1186,52],{"class":260},[158,1188,257],{"class":203},[158,1190,113],{"class":260},[158,1192,1193],{"class":203},"]})\n",[158,1195,1196,1199,1202,1205,1207,1211,1213,1216,1219,1221],{"class":160,"line":248},[158,1197,1198],{"class":199},"with",[158,1200,1201],{"class":203}," pd.ExcelWriter(",[158,1203,1204],{"class":168},"\"tasks.xlsx\"",[158,1206,257],{"class":203},[158,1208,1210],{"class":1209},"sa561","engine",[158,1212,220],{"class":199},[158,1214,1215],{"class":168},"\"xlsxwriter\"",[158,1217,1218],{"class":203},") ",[158,1220,1115],{"class":199},[158,1222,1223],{"class":203}," writer:\n",[158,1225,1226,1229,1232,1234,1237,1239,1242,1244,1247],{"class":160,"line":266},[158,1227,1228],{"class":203},"    tasks.to_excel(writer, ",[158,1230,1231],{"class":1209},"sheet_name",[158,1233,220],{"class":199},[158,1235,1236],{"class":168},"\"Tasks\"",[158,1238,257],{"class":203},[158,1240,1241],{"class":1209},"index",[158,1243,220],{"class":199},[158,1245,1246],{"class":260},"False",[158,1248,229],{"class":203},[158,1250,1251,1254,1256,1259,1261],{"class":160,"line":271},[158,1252,1253],{"class":203},"    book, sheet ",[158,1255,220],{"class":199},[158,1257,1258],{"class":203}," writer.book, writer.sheets[",[158,1260,1236],{"class":168},[158,1262,1263],{"class":203},"]\n",[158,1265,1266,1269,1271,1274,1276,1278,1280,1282,1284,1286,1288],{"class":160,"line":304},[158,1267,1268],{"class":203},"    alert ",[158,1270,220],{"class":199},[158,1272,1273],{"class":203}," book.add_format({",[158,1275,282],{"class":168},[158,1277,285],{"class":203},[158,1279,288],{"class":260},[158,1281,257],{"class":203},[158,1283,293],{"class":168},[158,1285,285],{"class":203},[158,1287,298],{"class":168},[158,1289,301],{"class":203},[158,1291,1292,1295,1297,1299,1301,1303,1306],{"class":160,"line":336},[158,1293,1294],{"class":203},"    calm ",[158,1296,220],{"class":199},[158,1298,1273],{"class":203},[158,1300,293],{"class":168},[158,1302,285],{"class":203},[158,1304,1305],{"class":168},"\"#0B6157\"",[158,1307,301],{"class":203},[158,1309,1310,1312,1315,1317],{"class":160,"line":1036},[158,1311,898],{"class":199},[158,1313,1314],{"class":203}," i, row ",[158,1316,904],{"class":199},[158,1318,1319],{"class":203}," tasks.iterrows():\n",[158,1321,1322,1325,1327,1330,1332,1335,1338,1341,1344,1347,1350],{"class":160,"line":1041},[158,1323,1324],{"class":203},"        fmt ",[158,1326,220],{"class":199},[158,1328,1329],{"class":203}," alert ",[158,1331,929],{"class":199},[158,1333,1334],{"class":203}," row[",[158,1336,1337],{"class":168},"\"status\"",[158,1339,1340],{"class":203},"] ",[158,1342,1343],{"class":199},"==",[158,1345,1346],{"class":168}," \"overdue\"",[158,1348,1349],{"class":199}," else",[158,1351,1352],{"class":203}," calm\n",[158,1354,1355,1358,1361,1364,1366,1368,1371,1373,1375,1378,1381,1384,1387,1390,1393,1396,1399],{"class":160,"line":1083},[158,1356,1357],{"class":203},"        sheet.write_rich_string(i ",[158,1359,1360],{"class":199},"+",[158,1362,1363],{"class":260}," 1",[158,1365,257],{"class":203},[158,1367,84],{"class":260},[158,1369,1370],{"class":203},", fmt, row[",[158,1372,1337],{"class":168},[158,1374,1175],{"class":203},[158,1376,1377],{"class":199},"f",[158,1379,1380],{"class":168},"\" (",[158,1382,1383],{"class":506},"{",[158,1385,1386],{"class":203},"row[",[158,1388,1389],{"class":168},"'days'",[158,1391,1392],{"class":203},"]",[158,1394,1395],{"class":506},"}",[158,1397,1398],{"class":168}," days)\"",[158,1400,229],{"class":203},[158,1402,1404,1407,1410,1412,1415],{"class":160,"line":1403},13,[158,1405,1406],{"class":203},"    sheet.set_column(",[158,1408,1409],{"class":168},"\"A:B\"",[158,1411,257],{"class":203},[158,1413,1414],{"class":260},"20",[158,1416,229],{"class":203},[158,1418,1420,1422,1425,1427,1430,1432],{"class":160,"line":1419},14,[158,1421,339],{"class":260},[158,1423,1424],{"class":203},"(pd.read_excel(",[158,1426,1204],{"class":168},[158,1428,1429],{"class":203},")[",[158,1431,1337],{"class":168},[158,1433,1434],{"class":203},"].tolist())\n",[158,1436,1438],{"class":160,"line":1437},15,[158,1439,1440],{"class":345},"# ['overdue (10 days)', 'on track (0 days)', 'overdue (3 days)']\n",[10,1442,1443,1444,1447],{},"Row and column numbers are zero-based in XlsxWriter, and row 0 is the header, hence ",[19,1445,1446],{},"i + 1",". Reading the file back shows the combined text — which is also what any formula, filter or pivot table sees. If readers need to filter by status, keep the plain status in its own column and use rich text only for the presentation column.",[144,1449,1451],{"id":1450},"step-5-keep-the-formatting-when-openpyxl-edits-the-file","Step 5 — Keep the formatting when openpyxl edits the file",[10,1453,1454,1455,1458],{},"Rich text is fragile in one specific way: openpyxl, by default, reads it as plain text, and saving the workbook writes it back plain. A later script that updates one cell with openpyxl silently strips every rich string in the workbook. Load with ",[19,1456,1457],{},"rich_text=True"," to keep it:",[149,1460,1462],{"className":190,"code":1461,"language":192,"meta":154,"style":154},"from openpyxl import load_workbook\n\nload_workbook(\"rich.xlsx\").save(\"rich_plain.xlsx\")                    # formatting lost\nload_workbook(\"rich.xlsx\", rich_text=True).save(\"rich_kept.xlsx\")     # formatting kept\nfor path in (\"rich_plain.xlsx\", \"rich_kept.xlsx\"):\n    value = load_workbook(path, rich_text=True)[\"Notes\"][\"A1\"].value\n    print(path, type(value).__name__)\n# rich_plain.xlsx str\n# rich_kept.xlsx CellRichText\n",[19,1463,1464,1477,1481,1500,1526,1546,1574,1593,1598],{"__ignoreMap":154},[158,1465,1466,1469,1472,1474],{"class":160,"line":161},[158,1467,1468],{"class":199},"from",[158,1470,1471],{"class":203}," openpyxl ",[158,1473,200],{"class":199},[158,1475,1476],{"class":203}," load_workbook\n",[158,1478,1479],{"class":160,"line":207},[158,1480,211],{"emptyLinePlaceholder":210},[158,1482,1483,1486,1488,1491,1494,1497],{"class":160,"line":214},[158,1484,1485],{"class":203},"load_workbook(",[158,1487,226],{"class":168},[158,1489,1490],{"class":203},").save(",[158,1492,1493],{"class":168},"\"rich_plain.xlsx\"",[158,1495,1496],{"class":203},")                    ",[158,1498,1499],{"class":345},"# formatting lost\n",[158,1501,1502,1504,1506,1508,1511,1513,1515,1517,1520,1523],{"class":160,"line":232},[158,1503,1485],{"class":203},[158,1505,226],{"class":168},[158,1507,257],{"class":203},[158,1509,1510],{"class":1209},"rich_text",[158,1512,220],{"class":199},[158,1514,288],{"class":260},[158,1516,1490],{"class":203},[158,1518,1519],{"class":168},"\"rich_kept.xlsx\"",[158,1521,1522],{"class":203},")     ",[158,1524,1525],{"class":345},"# formatting kept\n",[158,1527,1528,1530,1533,1535,1538,1540,1542,1544],{"class":160,"line":248},[158,1529,974],{"class":199},[158,1531,1532],{"class":203}," path ",[158,1534,904],{"class":199},[158,1536,1537],{"class":203}," (",[158,1539,1493],{"class":168},[158,1541,257],{"class":203},[158,1543,1519],{"class":168},[158,1545,883],{"class":203},[158,1547,1548,1551,1553,1556,1558,1560,1562,1564,1566,1569,1571],{"class":160,"line":266},[158,1549,1550],{"class":203},"    value ",[158,1552,220],{"class":199},[158,1554,1555],{"class":203}," load_workbook(path, ",[158,1557,1510],{"class":1209},[158,1559,220],{"class":199},[158,1561,288],{"class":260},[158,1563,1429],{"class":203},[158,1565,243],{"class":168},[158,1567,1568],{"class":203},"][",[158,1570,315],{"class":168},[158,1572,1573],{"class":203},"].value\n",[158,1575,1576,1579,1582,1585,1588,1591],{"class":160,"line":271},[158,1577,1578],{"class":260},"    print",[158,1580,1581],{"class":203},"(path, ",[158,1583,1584],{"class":260},"type",[158,1586,1587],{"class":203},"(value).",[158,1589,1590],{"class":260},"__name__",[158,1592,229],{"class":203},[158,1594,1595],{"class":160,"line":304},[158,1596,1597],{"class":345},"# rich_plain.xlsx str\n",[158,1599,1600],{"class":160,"line":336},[158,1601,1602],{"class":345},"# rich_kept.xlsx CellRichText\n",[10,1604,1605,1606,1608,1609,1612,1613,1616,1617,575],{},"With ",[19,1607,1457],{},", the cell value is a ",[19,1610,1611],{},"CellRichText"," made of plain strings and ",[19,1614,1615],{},"TextBlock"," objects, each carrying its font, so you can also inspect or build rich text with openpyxl directly. Other things a default openpyxl round trip can drop are listed in ",[24,1618,1620],{"href":1619},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fwhat-openpyxl-loses-when-saving-an-existing-workbook\u002F","What openpyxl Loses When You Save an Existing Workbook",[30,1622,39,1627,39,1630,39,1633,39,1636,39,1644,39,1654,39,1660,39,1664,39,1668,39,1675,39,1679,39,1684,39,1688,39,1693,39,1696,39,1703,39,1708,39,1712,39,1716,39,1721,39,1724,39,1729,39,1732],{"viewBox":1623,"role":33,"ariaLabelledBy":1624,"xmlns":37,"style":541},"4 0 752 207",[1625,1626],"rt-roundtrip-t","rt-roundtrip-d",[41,1628,1629],{"id":1625},"Opening a rich-text workbook with openpyxl",[45,1631,1632],{"id":1626},"Loading with the default settings reads rich cells as plain strings, and saving writes them back without their fragment formatting. Loading with rich_text True reads each cell as CellRichText with its text blocks and fonts, and saving keeps the formatting.",[49,1634],{"x":128,"y":52,"width":550,"height":1635,"fill":55},"207",[49,1637],{"x":554,"y":59,"width":1638,"height":1639,"rx":1640,"fill":1641,"stroke":1642,"style":1643},"351.0","175.0","14","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[1645,1646,1648],"g",{"transform":1647},"translate(182.5,29.0) scale(1.083)",[1649,1650],"path",{"d":1651,"fill":590,"stroke":1652,"style":1653},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[70,1655,1659],{"x":1656,"y":1657,"style":1658},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","load_workbook(path)",[160,1661],{"x1":586,"y1":1662,"x2":1663,"y2":1662,"stroke":1642,"style":64},"91.0","355.0",[70,1665,1667],{"x":561,"y":1666,"style":697},"111.0","rich cells become plain str",[1645,1669,1671],{"transform":1670},"translate(38.5,99.5) scale(0.625)",[1649,1672],{"d":1673,"fill":590,"stroke":101,"style":1674},"M6 6l12 12 M18 6L6 18","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[70,1676,1678],{"x":561,"y":1677,"style":697},"137.0","saving strips the formatting",[1645,1680,1682],{"transform":1681},"translate(38.5,125.5) scale(0.625)",[1649,1683],{"d":1673,"fill":590,"stroke":101,"style":1674},[70,1685,1687],{"x":561,"y":1686,"style":697},"163.0","fine for read-only analysis",[1645,1689,1691],{"transform":1690},"translate(38.5,151.5) scale(0.625)",[1649,1692],{"d":1651,"fill":590,"stroke":1642,"style":1674},[49,1694],{"x":1695,"y":59,"width":1638,"height":1639,"rx":1640,"fill":116,"stroke":117,"style":1643},"389.0",[1645,1697,1699],{"transform":1698},"translate(551.5,29.0) scale(1.083)",[1649,1700],{"d":1701,"fill":590,"stroke":1702,"style":1653},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[70,1704,1707],{"x":1705,"y":1657,"style":1706},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","load_workbook(path, rich_text=True)",[160,1709],{"x1":1710,"y1":1662,"x2":1711,"y2":1662,"stroke":117,"style":64},"405.0","724.0",[70,1713,1715],{"x":1714,"y":1666,"style":697},"429.0","CellRichText with TextBlocks",[1645,1717,1719],{"transform":1718},"translate(407.5,99.5) scale(0.625)",[1649,1720],{"d":1701,"fill":590,"stroke":117,"style":1674},[70,1722,1723],{"x":1714,"y":1677,"style":697},"fonts per fragment kept",[1645,1725,1727],{"transform":1726},"translate(407.5,125.5) scale(0.625)",[1649,1728],{"d":1701,"fill":590,"stroke":117,"style":1674},[70,1730,1731],{"x":1714,"y":1686,"style":697},"safe to edit and save",[1645,1733,1735],{"transform":1734},"translate(407.5,151.5) scale(0.625)",[1649,1736],{"d":1701,"fill":590,"stroke":117,"style":1674},[144,1738,1740],{"id":1739},"step-6-the-openpyxl-equivalent","Step 6 — The openpyxl equivalent",[10,1742,1743,1744,1746,1747,1750],{},"When a workbook is edited with openpyxl rather than created with XlsxWriter, build rich text from the same pieces openpyxl reads: plain strings and ",[19,1745,1615],{}," objects, each with an ",[19,1748,1749],{},"InlineFont",":",[149,1752,1754],{"className":190,"code":1753,"language":192,"meta":154,"style":154},"from openpyxl import Workbook\nfrom openpyxl.cell.rich_text import CellRichText, TextBlock\nfrom openpyxl.cell.text import InlineFont\n\nwb2 = Workbook()\nwb2.active[\"A1\"] = CellRichText(\n    \"Status: \", TextBlock(InlineFont(b=True, color=\"B91C1C\"), \"overdue\"), \" since 3 March\")\nwb2.save(\"rich_openpyxl.xlsx\")\nprint(load_workbook(\"rich_openpyxl.xlsx\", rich_text=True).active[\"A1\"].value)\n# Status: overdue since 3 March\n",[19,1755,1756,1767,1779,1791,1795,1805,1819,1855,1865,1890],{"__ignoreMap":154},[158,1757,1758,1760,1762,1764],{"class":160,"line":161},[158,1759,1468],{"class":199},[158,1761,1471],{"class":203},[158,1763,200],{"class":199},[158,1765,1766],{"class":203}," Workbook\n",[158,1768,1769,1771,1774,1776],{"class":160,"line":207},[158,1770,1468],{"class":199},[158,1772,1773],{"class":203}," openpyxl.cell.rich_text ",[158,1775,200],{"class":199},[158,1777,1778],{"class":203}," CellRichText, TextBlock\n",[158,1780,1781,1783,1786,1788],{"class":160,"line":214},[158,1782,1468],{"class":199},[158,1784,1785],{"class":203}," openpyxl.cell.text ",[158,1787,200],{"class":199},[158,1789,1790],{"class":203}," InlineFont\n",[158,1792,1793],{"class":160,"line":232},[158,1794,211],{"emptyLinePlaceholder":210},[158,1796,1797,1800,1802],{"class":160,"line":248},[158,1798,1799],{"class":203},"wb2 ",[158,1801,220],{"class":199},[158,1803,1804],{"class":203}," Workbook()\n",[158,1806,1807,1810,1812,1814,1816],{"class":160,"line":266},[158,1808,1809],{"class":203},"wb2.active[",[158,1811,315],{"class":168},[158,1813,1340],{"class":203},[158,1815,220],{"class":199},[158,1817,1818],{"class":203}," CellRichText(\n",[158,1820,1821,1824,1827,1830,1832,1834,1836,1839,1841,1844,1847,1849,1851,1853],{"class":160,"line":271},[158,1822,1823],{"class":168},"    \"Status: \"",[158,1825,1826],{"class":203},", TextBlock(InlineFont(",[158,1828,1829],{"class":1209},"b",[158,1831,220],{"class":199},[158,1833,288],{"class":260},[158,1835,257],{"class":203},[158,1837,1838],{"class":1209},"color",[158,1840,220],{"class":199},[158,1842,1843],{"class":168},"\"B91C1C\"",[158,1845,1846],{"class":203},"), ",[158,1848,326],{"class":168},[158,1850,1846],{"class":203},[158,1852,331],{"class":168},[158,1854,229],{"class":203},[158,1856,1857,1860,1863],{"class":160,"line":304},[158,1858,1859],{"class":203},"wb2.save(",[158,1861,1862],{"class":168},"\"rich_openpyxl.xlsx\"",[158,1864,229],{"class":203},[158,1866,1867,1869,1872,1874,1876,1878,1880,1882,1885,1887],{"class":160,"line":336},[158,1868,339],{"class":260},[158,1870,1871],{"class":203},"(load_workbook(",[158,1873,1862],{"class":168},[158,1875,257],{"class":203},[158,1877,1510],{"class":1209},[158,1879,220],{"class":199},[158,1881,288],{"class":260},[158,1883,1884],{"class":203},").active[",[158,1886,315],{"class":168},[158,1888,1889],{"class":203},"].value)\n",[158,1891,1892],{"class":160,"line":1036},[158,1893,1894],{"class":345},"# Status: overdue since 3 March\n",[10,1896,1897,1899,1900,1902,1903,1906,1907,1910,1911,1914,1915,1918],{},[19,1898,1749],{}," uses the short attribute names of the file format — ",[19,1901,1829],{}," for bold, ",[19,1904,1905],{},"i"," for italic, ",[19,1908,1909],{},"sz"," for size, ",[19,1912,1913],{},"vertAlign=\"superscript\""," for superscripts — rather than the ",[19,1916,1917],{},"Font"," names used for whole cells. The result is the same cell XlsxWriter would write, so choose the library by the job: XlsxWriter when generating a new report, openpyxl when changing an existing one.",[144,1920,1922],{"id":1921},"common-pitfalls","Common pitfalls",[1924,1925,1926,1937,1943,1949],"ul",{},[1927,1928,1929,1932,1933,1936],"li",{},[14,1930,1931],{},"Expecting fragments to be searchable separately."," Excel treats the cell as one string; ",[19,1934,1935],{},"COUNTIF"," for \"overdue\" needs wildcards, and filters see the whole text.",[1927,1938,1939,1942],{},[14,1940,1941],{},"Overusing it."," Rich text inside dense tables is hard to scan. Use it for notes, labels and single status cells; use conditional formatting for columns of values.",[1927,1944,1945,1948],{},[14,1946,1947],{},"Fonts that do not match the sheet."," Fragments without a format use the workbook's default font, not the cell's. If the column uses a different font or size, give every fragment an explicit format so the cell does not mix typefaces.",[1927,1950,1951,1954],{},[14,1952,1953],{},"Column width."," Rich strings do not auto-size. Set the width, and the row height for wrapped cells.",[144,1956,1958],{"id":1957},"conclusion","Conclusion",[10,1960,1961,1963,1964,1966,1967,1969,1970,1973,1974,1977,1978,1980],{},[19,1962,21],{}," builds a cell from fragments, each formatted by the format object before it, with an optional final format for the whole cell. Keep spaces inside fragments, use ",[19,1965,363],{}," for superscripts and subscripts, add ",[19,1968,533],{}," for line breaks, and check the return code — ",[19,1971,1972],{},"-5"," for too few items and ",[19,1975,1976],{},"-4"," for an empty fragment both leave the cell blank. When openpyxl edits the workbook later, load it with ",[19,1979,1457],{},", or the formatting disappears on save.",[144,1982,1984],{"id":1983},"frequently-asked-questions","Frequently asked questions",[10,1986,1987,1990],{},[14,1988,1989],{},"How do I make part of a cell bold with Python?","\nUse xlsxwriter's worksheet.write_rich_string(cell, \"Plain \", bold, \"bold part\", \" plain again\"). Each format object applies to the string fragment that follows it; fragments without a preceding format use the default font.",[10,1992,1993,1996],{},[14,1994,1995],{},"Why did write_rich_string write nothing?","\nIt returned an error code instead of raising. -5 means fewer than three fragments and formats were passed — a rich string needs at least one format between two pieces, or two formatted pieces. -4 means one of the fragments was an empty string, which Excel does not allow.",[10,1998,1999,2002],{},[14,2000,2001],{},"Can openpyxl read rich text?","\nYes, if you load the workbook with load_workbook(path, rich_text=True). The cell value is then a CellRichText made of text blocks with their fonts. With the default settings openpyxl reads plain text, and saving the workbook again removes the formatting.",[10,2004,2005,2008],{},[14,2006,2007],{},"How do I put a line break inside a rich string?","\nInclude \"\\n\" in a fragment and pass a cell format with text_wrap enabled as the last argument. Without wrapping, Excel shows the text on one line.",[144,2010,2012],{"id":2011},"related","Related",[1924,2014,2015,2021,2028,2035],{},[1927,2016,2017,2018,2020],{},"Up one level: ",[24,2019,27],{"href":26}," — report writing with XlsxWriter.",[1927,2022,2023,2027],{},[24,2024,2026],{"href":2025},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","Write a Formatted Excel Report with xlsxwriter"," — formats for whole cells and columns.",[1927,2029,2030,2034],{},[24,2031,2033],{"href":2032},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter\u002F","Apply Conditional Formatting with xlsxwriter"," — formatting that follows values.",[1927,2036,2037,2041],{},[24,2038,2040],{"href":2039},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fwrap-text-and-align-cells-with-openpyxl\u002F","Wrap Text and Align Cells with openpyxl"," — wrapping and alignment in detail.",[2043,2044,2045],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":154,"searchDepth":207,"depth":207,"links":2047},[2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058],{"id":146,"depth":207,"text":147},{"id":181,"depth":207,"text":182},{"id":356,"depth":207,"text":357},{"id":761,"depth":207,"text":762},{"id":1096,"depth":207,"text":1097},{"id":1450,"depth":207,"text":1451},{"id":1739,"depth":207,"text":1740},{"id":1921,"depth":207,"text":1922},{"id":1957,"depth":207,"text":1958},{"id":1983,"depth":207,"text":1984},{"id":2011,"depth":207,"text":2012},"2026-09-23","Put bold labels, coloured status words, superscripts and line breaks inside a single Excel cell with xlsxwriter's write_rich_string — the argument order, the silent return codes when a string is rejected, combining with pandas output, and keeping rich text intact when openpyxl edits the file later.","md",[2063,2065,2067,2069],{"q":1989,"a":2064},"Use xlsxwriter's worksheet.write_rich_string(cell, \"Plain \", bold, \"bold part\", \" plain again\"). Each format object applies to the string fragment that follows it; fragments without a preceding format use the default font.",{"q":1995,"a":2066},"It returned an error code instead of raising. -5 means fewer than three fragments and formats were passed — a rich string needs at least one format between two pieces, or two formatted pieces. -4 means one of the fragments was an empty string, which Excel does not allow.",{"q":2001,"a":2068},"Yes, if you load the workbook with load_workbook(path, rich_text=True). The cell value is then a CellRichText made of text blocks with their fonts. With the default settings openpyxl reads plain text, and saving the workbook again removes the formatting.",{"q":2007,"a":2070},"Include \"\\n\" in a fragment and pass a cell format with text_wrap enabled as the last argument. Without wrapping, Excel shows the text on one line.","write rich text with mixed formats in one cell with xlsxwriter",null,{"breadcrumb":2074},[2075,2078,2081],{"name":2076,"item":2077},"Home","\u002F",{"name":2079,"item":2080},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":27,"item":26},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter",{"title":2084,"description":2085},"Write Rich Text in One Excel Cell with xlsxwriter (write_rich_string)","Mixed formatting inside one Excel cell with xlsxwriter — write_rich_string fragments and formats, bold and coloured words, superscript and subscript, wrapped line breaks, return codes -4 and -5, pandas integration and preserving rich text with openpyxl rich_text=True.","write-rich-text-in-a-cell-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter\u002Findex",[2089,2090],"cell-styling","xlsxwriter","how-to","fnY8s-fwETUZW3u2Erm-BxLELct3AuT_DspkrXOOj5g",[2094,2098],{"title":2095,"path":2096,"stem":2097,"children":-1},"Write Dates, Times and Durations with XlsxWriter","\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-dates-times-and-durations-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-dates-times-and-durations-with-xlsxwriter\u002Findex",{"title":2099,"path":2100,"stem":2101,"children":-1},"Creating Charts in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Findex",[2103,2105],{"path":2104,"title":27},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter",{"path":2106,"title":2079},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2109,2113,2116,2119,2122,2125],{"path":2110,"title":2111,"kind":2112},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl","Styling Excel Cells with openpyxl","guide",{"path":2114,"title":2115,"kind":2091},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fadd-alternating-row-colors-with-openpyxl","Add Alternating Row Colors with openpyxl",{"path":2117,"title":2118,"kind":2091},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fadd-borders-to-a-range-with-openpyxl","Add Borders to a Range with openpyxl",{"path":2120,"title":2121,"kind":2091},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fadd-hyperlinks-to-excel-cells-with-python","Add Hyperlinks to Excel Cells with Python",{"path":2123,"title":2124,"kind":2091},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-a-reusable-style-theme-across-an-excel-report","Apply a Reusable Style Theme Across an Excel Report",{"path":2126,"title":2127,"kind":2091},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral","Apply Built-In Cell Styles Like Good, Bad and Neutral",1790972108792]