/* * Copyright (c) 2004, Nate Nielsen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and * the following disclaimer in the documentation and/or * other materials provided with the distribution. * * The names of contributors to this software may not be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * * CONTRIBUTORS * Nate Nielsen * */ #include "usuals.h" #include "xmlcomposer.h" #include "xmlfixups.h" #include "domhelpers.h" #include "tags.h" wstring formatInt(int num) { char buff[16]; // Certain OSs don't support swprintf :( sprintf(buff, "%d", num); wstring n; for(char* s = buff; *s; s++) n.append(1, *s); return n; } /* ---------------------------------------------------------------------------------- * CONSTRUCTION */ XmlComposer::XmlComposer(const XmlComposerOptions& options) { m_document = NULL; memcpy(&m_options, &options, sizeof(options)); // All autocounters start at 1 for(int i = 0; i < AUTOCOUNT_MAX; i++) m_autocount[i] = 1; } XmlComposer::~XmlComposer() { clear(); if(m_impl != NULL) m_impl.release(); } void XmlComposer::clear() { if(m_document != NULL) { try { m_document.release(); } catch(...) { } m_document = NULL; } LevelHandler::clear(); } /* ---------------------------------------------------------------------------------- * HANDLER OVERRIDES */ void XmlComposer::startDocument(RtfParser* reader) { LevelHandler::startDocument(reader); ASSERT(m_curLevel != NULL); // Create a new document m_document = m_impl.createDocument("", kElDoc, DOM::DocumentType()); // TODO: Throw error if document is null ASSERT(m_document != NULL); // Hook up the top level element DOM::Element top = m_document.getDocumentElement(); m_curLevel->setElement(top, true); // Set the attributes on the top level setAnalyser(AnalyserPtr(new Root)); setDestination(DestinationPtr(new Content)); getTextFormatting().resetPara(); getTextFormatting().resetText(); // Create the options element m_docOptions = m_document.createElement(kElOptions); top.appendChild(m_docOptions); } void XmlComposer::endDocument() { LevelHandler::endDocument(); XmlFixups fix; // Pass 0: Cleanup the tree // XmlFixups::combineDuplicates(m_document); // XmlFixups::consolidateStartTags(m_document); // XmlFixups::consolidateEndTags(m_document); // Pass 1: Block breakout fix.breakTables(m_document); fix.breakTags(m_document, kElTable, kElRow); fix.breakTags(m_document, kElRow, kElCell); fix.wrapTags(m_document, kElCell, kElDest); fix.breakBlocks(m_document); fix.breakLists(m_document); // Pass 2: Fixups fix.runPassTwo(m_document); // XmlFixups::fixLists(m_document); // XmlFixups::fixStyles(m_document); // XmlFixups::fixBlocks(m_document); // XmlFixups::removeTags(m_document); // XmlFixups::breakBreak(m_document, kElDoc, kElPage); // XmlFixups::breakBreak(m_document, kElDoc, kElSect); // Pass 3: Final cleanup // XmlFixups::combineDuplicates(m_document); DOM::Element top = m_document.getDocumentElement(); ASSERT(top != NULL); top.setAttribute(kNSPrefix, kNSRtfx); return; } void XmlComposer::charData(wstring data) { ASSERT(m_curLevel != NULL); DestinationPtr destination = m_curLevel->getDestination(); if(!destination) { destination = DestinationPtr(new Content); setDestination(destination); } destination->charData(data); } void XmlComposer::controlWord(const string& cw, int flags, int param) { ASSERT(m_curLevel != NULL); AnalyserPtr analyser = m_curLevel->getAnalyser(); if(analyser) analyser->controlWord(cw, flags, param); } void XmlComposer::groupStart() { LevelHandler::groupStart(); ASSERT(m_curLevel != NULL); AnalyserPtr analyser = m_curLevel->getAnalyser(); if(analyser) analyser->groupStart(); } void XmlComposer::groupEnd() { LevelHandler::groupEnd(); ASSERT(m_curLevel != NULL); AnalyserPtr analyser = m_curLevel->getAnalyser(); if(analyser) analyser->groupEnd(); } /* ---------------------------------------------------------------------------------- * HELPER FUNCTIONS */ DOM::Element XmlComposer::createElement(const string& name) { ASSERT(name.length() > 0); return m_document.createElement(name); } void XmlComposer::replaceElement(const DOM::Element& element) { ASSERT(m_curLevel != NULL); m_curLevel->setElement(element, true); } void XmlComposer::pushElement(const DOM::Element& element) { ASSERT(m_curLevel != NULL); getElement().appendChild(element); m_curLevel->setElement(element); } void XmlComposer::pushTopElement(const DOM::Element& element) { ASSERT(m_curLevel != NULL); DOM::Element top = m_document.getDocumentElement(); if(top.hasChildNodes()) top.insertBefore(element, top.getFirstChild()); else top.appendChild(element); m_curLevel->setElement(element); } DOM::Element XmlComposer::popElement() { DOM::Element element = getElement(); ASSERT(m_curLevel != NULL); DOM::Node parent = element.getParentNode(); ASSERT(parent != NULL && parent.getNodeType() == DOM::Node::ELEMENT_NODE); // Set it deep so it replaces the current element m_curLevel->setElement((DOM::Element&)parent, true); return element; } void XmlComposer::setAttribute(const string& name, const wstring& value, DOM::Element el) { ASSERT(name.length() > 0); if(el == NULL) el = getElement(); el.setAttribute(name, value); } void XmlComposer::setAttribute(const string& name, int value, DOM::Element el) { ASSERT(name.length() > 0); if(el == NULL) el = getElement(); el.setAttribute(name, formatInt(value)); } void XmlComposer::setDestination(DestinationPtr dest) { ASSERT(m_curLevel); m_curLevel->setDestination(dest); dest->m_composer = this; dest->initialize(); } DestinationPtr XmlComposer::replaceDestination(DestinationPtr dest) { ASSERT(m_curLevel); DestinationPtr old = m_curLevel->getDestination(); m_curLevel->setDestination(dest, true); dest->m_composer = this; dest->initialize(); return old; } void XmlComposer::setAnalyser(AnalyserPtr analy) { ASSERT(m_curLevel); ASSERT(analy != NULL); analy->m_composer = this; m_curLevel->setAnalyser(analy); analy->initialize(); } AnalyserPtr XmlComposer::getAnalyser() { ASSERT(m_curLevel); return m_curLevel->getAnalyser(); } DestinationPtr XmlComposer::getDestination() { ASSERT(m_curLevel); return m_curLevel->getDestination(); } RtfFormatting& XmlComposer::getTextFormatting() { ASSERT(m_curLevel); return m_curLevel->getFormatting(); } int XmlComposer::getAutoCount(int type) { ASSERT(type < AUTOCOUNT_MAX); return m_autocount[type]; } void XmlComposer::incrementAutoCount(int type) { ASSERT(type < AUTOCOUNT_MAX); m_autocount[type]++; } void XmlComposer::addDocumentOption(DOM::Element& option) { ASSERT(m_docOptions != NULL); m_docOptions.appendChild(option); } /* ---------------------------------------------------------------------------------- * CONVENIENCE MACROS USED BELOW */ #define AN_ELEMENT(name) \ m_composer->pushElement(m_composer->createElement(name)) #define AN_TOP_ELEMENT(name) \ m_composer->pushTopElement(m_composer->createElement(name)) #define AN_POP_ELEMENT() \ m_composer->popElement() #define AN_ATTRIBUTE(name, value) \ m_composer->setAttribute(name, value) #define AN_DESTINATION_ATTR(name) \ m_composer->setDestination(new Attribute(name)) #define AN_DESTINATION(cls) \ m_composer->setDestination(new cls) #define AN_ANALYSER(cls) \ m_composer->setAnalyser(AnalyserPtr(new cls)) #define AN_SET_ANALYSER(cls) \ m_composer->setAnalyser(AnalyserPtr(cls)) #define HAS_PARAM (flags & kHasParam) #define DEFAULT_CONTROLWORD processDefault(cw, flags, param) #define DUMMY 1 == 1 #define NUM_ATTR(x) formatInt(x) #define DO_EXTRAS() (m_composer->getOptions().extras) /* ---------------------------------------------------------------------------------- * BASE ANALYSER */ bool XmlComposer::BaseAnalyser::processDefault(const string& cw, int flags, int param) { // Unicode blocks go to a special analyser if(cw == "upr") { AnalyserPtr analy = m_composer->getAnalyser(); ASSERT(analy != NULL); AN_SET_ANALYSER(new Upr(analy)); return true; } return false; } void XmlComposer::BaseAnalyser::applyParaFormatting(RtfFormatting* format, DOM::Element& el) { if(format == NULL) format = &(m_composer->getTextFormatting()); wstring fix = kValPara; // Is it a list? int list = format->paraList(); if(list != -1) el.setAttribute(kAtList, NUM_ATTR(list)); else el.removeAttribute(kAtList); // Is it a cell? if(format->paraInTable()) el.setAttribute(kAtCell, L"1"); else el.removeAttribute(kAtCell); // Paragraph styles int style = format->paraStyle(); if(style != -1) el.setAttribute(kElStyle, NUM_ATTR(style)); else el.removeAttribute(kElStyle); // These fix elements are later picked up in XmlFixups::fixBlocks el.setAttribute(kAtFix, fix); } DOM::Element XmlComposer::BaseAnalyser::getCurrentBlock() { DOM::Node node = m_composer->getElement(); if(node.hasChildNodes()) node = node.getLastChild(); return DOMHelpers::getPriorElement(node, kElBlock); } bool XmlComposer::BaseAnalyser::processTextContent(const string& cw, int flags, int param) { DOM::Element el; bool process = false; RtfFormatting& format = m_composer->getTextFormatting(); // New paragraph if(cw == "par") { el = getCurrentBlock(); if(el != NULL) applyParaFormatting(&format, el); el = m_composer->createElement(kElBlock); applyParaFormatting(&format, el); } // Cells (used later in applyParaFormatting) else if(cw == "intbl") format.paraSetTable(true); // Start of a cell else if(cw == "cell") { el = getCurrentBlock(); if(el != NULL) applyParaFormatting(&format, el); el = m_composer->createElement(kElCell); m_composer->pushElement(el); m_composer->popElement(); el = m_composer->createElement(kElBlock); applyParaFormatting(&format, el); } // Start of a row else if(cw == "trowd") el = m_composer->createElement(kElRow); // A tab else if(cw == "tab") el = m_composer->createElement(kElTab); // A section break else if(cw == "sect") el = m_composer->createElement(kElSect); // A page break else if(cw == "page") el = m_composer->createElement(kElPage); // A paragraph style else if(cw == "s" && HAS_PARAM) format.paraSetStyle(param); // A line break else if(cw == "line") el = m_composer->createElement(kElLine); // A page header (not implemented) else if(cw == "header") AN_ANALYSER(Skip); // A page footer (not implemented) else if(cw == "footer") AN_ANALYSER(Skip); // A bookmark (not implemented) else if(cw == "bkmkstart") AN_ANALYSER(Skip); // List text (not implemented) else if(cw == "listtext") AN_ANALYSER(Skip); // Set list style (used in applyFormatting) else if(cw == "ls" && HAS_PARAM) format.paraSetList(param); if(el != NULL) { // This ensures that our content destination is open and ready DestinationPtr dest = m_composer->getDestination(); ASSERT(dest != NULL); dest->charData(kValNull); m_composer->pushElement(el); m_composer->popElement(); } return (el != NULL) || process; } bool XmlComposer::BaseAnalyser::processTextFormatting(const string& cw, int flags, int param, RtfFormatting& format) { bool on = true; if(flags & HAS_PARAM && param == 0) on = false; // Clears all paragraph formatting if(cw == "pard") { format.resetPara(); // applyParaFormatting(); } // Rest are pretty much self-explanatory else if(cw == "plain") format.resetText(); else if(cw == "b") format.textSetBold(on); else if(cw == "i") format.textSetItalic(on); else if(cw == "v") format.textSetHidden(on); else if(cw == "ul") format.textSetUnderline(on); else if(cw == "super") format.textSetSuScript(RtfFormatting::SUPERSCRIPT); else if(cw == "sub") format.textSetSuScript(RtfFormatting::SUBSCRIPT); else if(cw == "cf" && HAS_PARAM) format.textSetColor(param); else if(cw == "f" && HAS_PARAM) format.textSetFont(param); else if(cw == "fs" && HAS_PARAM) format.textSetFontSize(param); else return false; return true; } bool XmlComposer::BaseAnalyser::processTextFormatting(const string& cw, int flags, int param) { return processTextFormatting(cw, flags, param, m_composer->getTextFormatting()); } bool XmlComposer::BaseAnalyser::processTextAutoContent(const string& cw, int flags, int param) { DestinationPtr dest = m_composer->getDestination(); ASSERT(dest != NULL); dest->charData(kValNull); // Auto generated content if(cw == "chftn") { // Footnote auto numbering int ac = m_composer->getAutoCount(AUTOCOUNT_FOOTNOTE); AN_ELEMENT(kElRef); AN_ATTRIBUTE(kAtType, kValFootNote); AN_ATTRIBUTE(kAtTo, ac); // Only output the actual link text in this case if(DO_EXTRAS()) dest->charData(formatInt(ac)); AN_POP_ELEMENT(); return true; } return false; } /* ---------------------------------------------------------------------------------- * ANALYSER/DESTINATION DEFINITIONS */ #define ON_INITIALIZE(cls) \ void XmlComposer::cls::initialize() #define ON_CONTROLWORD(cls) \ void XmlComposer::cls::controlWord(const string& cw, int flags, int param) #define ON_CHARDATA(cls) \ void XmlComposer::cls::charData(wstring data) #define ON_GROUPSTART(cls) \ void XmlComposer::cls::groupStart() #define ON_GROUPEND(cls) \ void XmlComposer::cls::groupEnd() #define ON_DONE(cls) \ void XmlComposer::cls::done() // Skip Analyser -------------------------------------------------------------------- ON_INITIALIZE(Skip) { AN_DESTINATION(Null); } ON_GROUPSTART(Skip) { AN_ANALYSER(Skip); } // Upr Analyser --------------------------------------------------------------------- XmlComposer::Upr::Upr(AnalyserPtr prv) { ASSERT(prv); prev = prv; } ON_GROUPSTART(Upr) { AN_ANALYSER(Skip); } ON_GROUPEND(Upr) { ASSERT(prev); m_composer->setAnalyser(prev); prev = NULL; } // Stylesheet Analyser -------------------------------------------------------------- ON_INITIALIZE(Stylesheet) { AN_TOP_ELEMENT(kElStylesheet); } ON_GROUPSTART(Stylesheet) { // Each group should be a style AN_ANALYSER(Style); // Without any character data AN_DESTINATION(Null); } // Stylesheet Style Analyser -------------------------------------------------------- ON_INITIALIZE(Style) { // Were not sure if this element is really something // so we can't always create haveStyle = false; } ON_CONTROLWORD(Style) { // Get the style id if(flags & kAsterisk) { AN_ANALYSER(Skip); return; } // Create the style tag if necessary if(!haveStyle) { AN_ELEMENT(kElStyle); AN_DESTINATION_ATTR(kAtName); haveStyle = true; } // The style id if(cw == "s" && flags & kHasParam) { AN_ATTRIBUTE(kAtId, param); } // Otherwise get as much formatting out of the tag as possible else if(processTextFormatting(cw, flags, param)) DUMMY; else DEFAULT_CONTROLWORD; } ON_GROUPSTART(Style) { AN_ANALYSER(Skip); } ON_GROUPEND(Style) { RtfFormatting& props = m_composer->getTextFormatting(); // Dig out all the formatting attributes if(props.textIsBold()) AN_ATTRIBUTE(kAtBold, kValTrue); if(props.textIsHidden()) AN_ATTRIBUTE(kAtHidden, kValTrue); if(props.textIsItalic()) AN_ATTRIBUTE(kAtItalic, kValTrue); if(props.textIsStrike()) AN_ATTRIBUTE(kAtStrike, kValTrue); if(props.textIsUnderline()) AN_ATTRIBUTE(kAtUnderline, kValTrue); // TODO: Do fonts and colors here } // Font Table Analyser -------------------------------------------------------------- ON_INITIALIZE(FontTable) { AN_TOP_ELEMENT(kElFontTable); AN_DESTINATION(Null); } ON_GROUPSTART(FontTable) { // Each group should be a font AN_ANALYSER(Font); } ON_DONE(FontTable) { DOM::Element deffont = m_composer->createElement(kElFont); // Default font is always the first in the list deffont->setAttribute(kAtId, kValZero); // Default size is always 12 pt deffont->setAttribute(kAtSize, L"12"); // TODO: Is this correct? deffont->setAttribute(kAtColor, kValZero); m_composer->addDocumentOption(deffont); } // Font Analyser -------------------------------------------------------------------- ON_INITIALIZE(Font) { AN_ELEMENT(kElFontDef); AN_DESTINATION_ATTR(kAtName); } ON_CONTROLWORD(Font) { // The font id if(cw == "f" && flags & kHasParam) AN_ATTRIBUTE(kAtId, param); else DEFAULT_CONTROLWORD; } ON_GROUPSTART(Font) { AN_ANALYSER(Skip); } // List Table Analyser -------------------------------------------------------------- ON_INITIALIZE(ListTable) { AN_TOP_ELEMENT(kElListtable); } ON_GROUPSTART(ListTable) { // Everything in here should be a list AN_ANALYSER(List); // Content doesn't matter AN_DESTINATION(Null); } // List (in List Table) Analyser ---------------------------------------------------- ON_INITIALIZE(List) { // Create a default element AN_ELEMENT(kElListdef); if(DO_EXTRAS()) AN_ATTRIBUTE(kAtType, kValDisc); AN_ATTRIBUTE(kAtOrdered, L"0"); levelsSeen = 0; } ON_CONTROLWORD(List) { // The name if(cw == "listname") AN_DESTINATION_ATTR(kAtName); // The list id else if(cw == "listid" && HAS_PARAM) AN_ATTRIBUTE(kAtId, param); // We let listlevel in here too else if(cw == "levelstartat" && HAS_PARAM) AN_ATTRIBUTE(kAtStart, param); // The list type else if(cw == "levelnfc" && HAS_PARAM) { if(DO_EXTRAS()) { switch(param) { case 0: // 1, 2, 3 case 5: // 1st, 2nd, 3rd case 6: // One, Two, Three case 7: // First, Second, Third case 22: // 01, 02, 03 AN_ATTRIBUTE(kAtType, kValArabic); break; case 1: // I, II, III AN_ATTRIBUTE(kAtType, kValUpperRoman); break; case 2: // i, ii, iii AN_ATTRIBUTE(kAtType, kValLowerRoman); break; case 3: // A, B, C AN_ATTRIBUTE(kAtType, kValUpperAlpha); break; case 4: // a, b, c AN_ATTRIBUTE(kAtType, kValLowerAlpha); break; default: AN_ATTRIBUTE(kAtType, kValDisc); break; } } switch(param) { case 0: case 5: case 6: case 7: case 22: case 1: case 2: case 3: case 4: AN_ATTRIBUTE(kAtOrdered, L"1"); break; default: AN_ATTRIBUTE(kAtOrdered, L"0"); } } else DEFAULT_CONTROLWORD; } ON_GROUPSTART(List) { // Skip internal groups and content if(levelsSeen > 0) AN_ANALYSER(Skip); levelsSeen++; } // The List Override Table ---------------------------------------------------------- ON_INITIALIZE(ListOverrideTable) { // Get all of the current lists DOM::Document document = m_composer->getDocument(); lists = document.getElementsByTagName(kElListdef); curList = NULL; lsId = -1; } ON_GROUPSTART(ListOverrideTable) { // Content doesn't matter AN_DESTINATION(Null); } ON_CONTROLWORD(ListOverrideTable) { // New list override clear if(cw == "listoverride") curList = NULL; // List id for current listoverride else if(cw == "listid" && HAS_PARAM) { wstring id = formatInt(param); if(lists != NULL) { // Find the list in question for(int i = 0; i < lists->getLength(); i++) { DOM::Node node = lists->item(i); if(node != NULL && node.getNodeType() == DOM::Node::ELEMENT_NODE) { DOM::Element element = (DOM::Element&)node; if(element.getAttribute(kAtId) == id) { curList = element; break; } } } } } // The actual list code else if(cw == "ls" && HAS_PARAM) lsId = param; // Override the starting level for the node else if(cw == "levelstartat" && HAS_PARAM) { if(curList != NULL) curList.setAttribute(kAtStart, NUM_ATTR(param)); } else DEFAULT_CONTROLWORD; // Okay before any overrides take effect we need to duplicate // the list node for overriding, using the 'listid' and 'ls' we gathered if(curList != NULL && lsId != -1) { DOM::Element parent = (const DOM::Element&)curList.getParentNode(); if(parent != NULL) { curList = (const DOM::Element&)curList.cloneNode(true); if(curList != NULL) { parent.appendChild(curList); curList.setAttribute(kAtId, NUM_ATTR(lsId)); } } lsId = -1; } } // Info Block Analyser -------------------------------------------------------------- ON_INITIALIZE(Info) { // Create a new element AN_TOP_ELEMENT(kElInfo); AN_DESTINATION(Null); } ON_CONTROLWORD(Info) { if(cw == "title") { AN_ELEMENT(kElTitle); AN_DESTINATION(Raw); } else if(cw == "author") { AN_ELEMENT(kElAuthor); AN_DESTINATION(Raw); } else if(cw == "operator") { AN_ELEMENT(kElOperator); AN_DESTINATION(Raw); } else if(flags & kAsterisk) AN_ANALYSER(Skip); else DEFAULT_CONTROLWORD; } // Root Analyser -------------------------------------------------------------------- ON_INITIALIZE(Root) { } ON_CONTROLWORD(Root) { // All the main RTF sections if(cw == "stylesheet") AN_ANALYSER(Stylesheet); else if(cw == "listtable") AN_ANALYSER(ListTable); else if(cw == "listoverridetable") AN_ANALYSER(ListOverrideTable); else if(cw == "info") AN_ANALYSER(Info); else if(cw == "fonttbl") { if(DO_EXTRAS()) AN_ANALYSER(FontTable); else AN_ANALYSER(Skip); } else if(cw == "colortbl") AN_ANALYSER(Skip); else if(cw == "footnote") AN_ANALYSER(FootNote); else if(cw == "pict") { AN_ANALYSER(Skip); AN_DESTINATION(Null); } else if(flags & kAsterisk) AN_ANALYSER(Skip); else if(processTextContent(cw, flags, param)) DUMMY; else if(processTextAutoContent(cw, flags, param)) DUMMY; else if(processTextFormatting(cw, flags, param)) DUMMY; else DEFAULT_CONTROLWORD; } // Content Destination -------------------------------------------------------------- ON_INITIALIZE(Content) { parent = m_composer->getElement(); created = false; } ON_CHARDATA(Content) { // Create the first time we get content if(!created) { DOM::Element dest = m_composer->createElement(kElDest); parent.appendChild(dest); m_composer->replaceElement(dest); DOM::Element el = m_composer->createElement(kElBlock); m_composer->pushElement(el); m_composer->popElement(); created = true; } if(data.length() == 0) return; int elements = 0; RtfFormatting& format = m_composer->getTextFormatting(); // Extra elements written out here are consolidated in // XmlFixups::combineDuplicates // Handle fonts etc... if(DO_EXTRAS()) { int font = format.textFont(); int fontsize = format.textFontSize(); if(font != -1 || fontsize != -1) { AN_ELEMENT(kElFont); if(font != -1) AN_ATTRIBUTE(kAtId, font); if(fontsize != -1) { // Little hack for half point sizes wstring size = NUM_ATTR(fontsize / 2); if(fontsize % 2) size.append(L".5"); AN_ATTRIBUTE(kAtSize, size); } if(format.textColor() != -1) AN_ATTRIBUTE(kAtColor, format.textColor()); elements++; } } // Now do text Properties if necessary if(format.textIsBold()) { AN_ELEMENT(kElB); elements++; } if(format.textIsHidden()) { AN_ELEMENT(kElHide); elements++; } if(format.textIsItalic()) { AN_ELEMENT(kElI); elements++; } if(format.textIsStrike()) { AN_ELEMENT(kElStrike); elements++; } if(format.textIsUnderline()) { AN_ELEMENT(kElU); elements++; } if(format.textSuScript() == RtfFormatting::SUPERSCRIPT) { AN_ELEMENT(kElSuper); elements++; } if(format.textSuScript() == RtfFormatting::SUBSCRIPT) { AN_ELEMENT(kElSub); elements++; } // Write the data to the element m_composer->getElement().appendChild( m_composer->getDocument().createTextNode(data)); // Now drop out of all the above formatting while(elements-- > 0) AN_POP_ELEMENT(); } // FootNote Analyser ---------------------------------------------------------------- ON_INITIALIZE(FootNote) { int ac = m_composer->getAutoCount(AUTOCOUNT_FOOTNOTE); AN_ELEMENT(kElFootNote); AN_ATTRIBUTE(kAtId, ac); AN_DESTINATION(Content); } ON_CONTROLWORD(FootNote) { // Inside foot notes there's no link to the foot note if(cw == "chftn") { // Only put actual text when in extras mode if(DO_EXTRAS()) { DestinationPtr dest = m_composer->getDestination(); ASSERT(dest != NULL); int ac = m_composer->getAutoCount(AUTOCOUNT_FOOTNOTE); dest->charData(formatInt(ac)); } return; } // Process text content in the foot note else if(processTextContent(cw, flags, param)) DUMMY; else if(processTextAutoContent(cw, flags, param)) DUMMY; else if(processTextFormatting(cw, flags, param)) DUMMY; else DEFAULT_CONTROLWORD; } ON_DONE(FootNote) { m_composer->incrementAutoCount(AUTOCOUNT_FOOTNOTE); } // Raw Destination ------------------------------------------------------------------ ON_CHARDATA(Raw) { // Write the data to the element m_composer->getElement().appendChild( m_composer->getDocument().createTextNode(data)); } // Attribute Destination ------------------------------------------------------------ ON_INITIALIZE(Attribute) { element = m_composer->getElement(); ASSERT(element != NULL); } ON_CHARDATA(Attribute) { // Get the current value wstring cur = element.getAttribute(name); if(data.at(data.size() - 1) == L';') data.resize(data.size() - 1); // Append data cur.append(data); // Write it back element.setAttribute(name, cur); }