summaryrefslogtreecommitdiff
path: root/src/xmlcomposer.cpp
diff options
context:
space:
mode:
authorStef <stef@ws.local>2003-09-17 18:34:42 +0000
committerStef <stef@ws.local>2003-09-17 18:34:42 +0000
commit53914f770f1e1dc1ab4342c64846fd995825b7e6 (patch)
tree63d14dacbd3d81363fcbea1036c47a0210b0f397 /src/xmlcomposer.cpp
parent15f3015d2e8305b729d7996faad410b3378497da (diff)
Initial Import
Diffstat (limited to 'src/xmlcomposer.cpp')
-rw-r--r--src/xmlcomposer.cpp1811
1 files changed, 1811 insertions, 0 deletions
diff --git a/src/xmlcomposer.cpp b/src/xmlcomposer.cpp
new file mode 100644
index 0000000..1bfeb30
--- /dev/null
+++ b/src/xmlcomposer.cpp
@@ -0,0 +1,1811 @@
+// RtfParser.cpp: implementation of the RtfParser class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "stdafx.h"
+#include "RtfAnalyser.h"
+
+const char* kElDest = "i_dest";
+const char* kElBlock = "i_block";
+const char* kAtFix = "i_fix";
+const char* kAtCell = "i_cell";
+const char* kElListtable = "i_listtable";
+const char* kElListdef = "i_listdef";
+
+const char* kElPara = "para";
+const char* kElDoc = "document";
+const char* kElTab = "tab";
+const char* kElSect = "sect";
+const char* kElPage = "page";
+const char* kElStyle = "style";
+const char* kElLine = "line";
+const char* kElList = "list";
+const char* kElStylesheet = "stylesheet";
+const char* kElInfo = "info";
+const char* kElTitle = "title";
+const char* kElAuthor = "author";
+const char* kElOperator = "operator";
+const char* kElB = "b";
+const char* kElHide = "hide";
+const char* kElI = "i";
+const char* kElStrike = "strike";
+const char* kElU = "u";
+const char* kElColor = "color";
+const char* kElCell = "cell";
+const char* kElRow = "row";
+const char* kElTable = "table";
+
+const char* kAtList = "list";
+const char* kAtName = "name";
+const char* kAtBold = "bold";
+const char* kAtHidden = "hide";
+const char* kAtItalic = "italic";
+const char* kAtStrike = "strike";
+const char* kAtUnderline = "underline";
+const char* kAtColor = "color";
+const char* kAtType = "type";
+const char* kAtOrdered = "ordered";
+const char* kAtStart = "start";
+const char* kAtId = "id";
+const char* kAtIndex = "id";
+
+const wchar_t* kValDisc = L"disc";
+const wchar_t* kValLowerAlpha = L"lower-alpha";
+const wchar_t* kValUpperAlpha = L"upper-alpha";
+const wchar_t* kValLowerRoman = L"lower-roman";
+const wchar_t* kValUpperRoman = L"upper-roman";
+const wchar_t* kValArabic = L"arabic";
+const wchar_t* kValNull = L"";
+
+const wchar_t* kValList = L"list";
+const wchar_t* kValPara = L"para";
+const wchar_t* kValTable = L"table";
+
+const char* kNoDuplicates[] =
+ { kElB, kElU, kElI, kElColor, kElHide, kElColor, NULL };
+
+const char* kRemoveTags[] =
+ { kElDest, kElListdef, kElListtable, NULL };
+
+const char* kBlockTags[] =
+ { kElTable, kElPara, NULL };
+
+const char* kHideList[] =
+ { kAtId, kAtList, NULL };
+
+const char* kNSPrefix = "xmlns";
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+RtfParser::RtfParser(const RtfParserOptions& options)
+{
+ m_document = NULL;
+ memcpy(&m_options, &options, sizeof(options));
+}
+
+RtfParser::~RtfParser()
+{
+ clear();
+
+ if(m_impl != NULL)
+ m_impl.release();
+}
+
+void RtfParser::clear()
+{
+ if(m_document != NULL)
+ {
+ try
+ {
+ m_document.release();
+ }
+ catch(...) { }
+
+ m_document = NULL;
+ }
+ LevelHandler::clear();
+}
+
+void RtfParser::startDocument(RtfReader* reader)
+{
+ LevelHandler::startDocument(reader);
+
+ // Create a new document
+ m_document = m_impl.createDocument("", kElDoc, DOM::DocumentType());
+
+ // TODO: Throw error if document is null
+ ASSERT(m_document != NULL);
+
+ ASSERT(m_curLevel != NULL);
+ m_curLevel->setElement(m_document.getDocumentElement(), true);
+
+ // Set the attributes on the top level
+ setAnalyser(AnalyserPtr(new Root));
+ setDestination(DestinationPtr(new Content));
+ getTextFormatting().resetPara();
+ getTextFormatting().resetText();
+}
+
+void RtfParser::endDocument()
+{
+ LevelHandler::endDocument();
+
+ // Cleanup the tree
+ removeDuplicates(m_document);
+ breakTables(m_document);
+ breakTags(m_document, kElTable, kElRow);
+ breakTags(m_document, kElRow, kElCell);
+ wrapTags(m_document, kElCell, kElDest);
+ breakBlocks(m_document);
+ breakLists(m_document);
+ fixLists(m_document);
+ fixStyles(m_document);
+ fixBlocks(m_document);
+ removeTags(m_document);
+ breakBreak(m_document, kElDoc, kElPage);
+ breakBreak(m_document, kElDoc, kElSect);
+ return;
+}
+
+
+
+
+
+// -----------------------------------------------------------------------
+// Helper functions
+
+DOM::Element RtfParser::createElement(const string& name)
+{
+ ASSERT(name.length() > 0);
+ return m_document.createElement(name);
+
+ // TODO: Throw exception here if necessary
+}
+
+void RtfParser::replaceElement(const DOM::Element& element)
+{
+ ASSERT(m_curLevel != NULL);
+ m_curLevel->setElement(element, true);
+}
+
+void RtfParser::pushElement(const DOM::Element& element)
+{
+ ASSERT(m_curLevel != NULL);
+ getElement().appendChild(element);
+ m_curLevel->setElement(element);
+}
+
+DOM::Element RtfParser::popElement()
+{
+ DOM::Element element = getElement();
+ ASSERT(m_curLevel != NULL);
+
+ DOM::Node parent = element.getParentNode();
+ ASSERT(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 RtfParser::setAttribute(const string& name, const wstring& value, DOM::Element el)
+{
+ ASSERT(name.length() > 0);
+ if(el == NULL)
+ el = getElement();
+ el.setAttribute(name, value);
+}
+
+void RtfParser::setDestination(DestinationPtr dest)
+{
+ ASSERT(m_curLevel);
+
+ m_curLevel->setDestination(dest);
+ dest->m_parser = this;
+ dest->initialize();
+}
+
+DestinationPtr RtfParser::replaceDestination(DestinationPtr dest)
+{
+ ASSERT(m_curLevel);
+
+ DestinationPtr old = m_curLevel->getDestination();
+ m_curLevel->setDestination(dest, true);
+ dest->m_parser = this;
+ dest->initialize();
+
+ return old;
+}
+
+
+void RtfParser::setAnalyser(AnalyserPtr analy)
+{
+ ASSERT(m_curLevel);
+ ASSERT(analy != NULL);
+
+ analy->m_parser = this;
+ m_curLevel->setAnalyser(analy);
+ analy->initialize();
+}
+
+AnalyserPtr RtfParser::getAnalyser()
+{
+ ASSERT(m_curLevel);
+ return m_curLevel->getAnalyser();
+}
+
+DestinationPtr RtfParser::getDestination()
+{
+ ASSERT(m_curLevel);
+ return m_curLevel->getDestination();
+}
+
+RtfFormatting& RtfParser::getTextFormatting()
+{
+ ASSERT(m_curLevel);
+ return m_curLevel->getFormatting();
+}
+
+
+// ---------------------------------------------------------------------------------
+// Pass this stuff on through to the appropriate analysers etc...
+
+void RtfParser::charData(wstring data)
+{
+ ASSERT(m_curLevel != NULL);
+ DestinationPtr destination = m_curLevel->getDestination();
+ if(destination)
+ {
+ destination->charData(data);
+ }
+ else
+ {
+ destination = DestinationPtr(new Content);
+ setDestination(destination);
+ }
+
+}
+
+void RtfParser::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 RtfParser::groupStart()
+{
+ LevelHandler::groupStart();
+
+ ASSERT(m_curLevel != NULL);
+ AnalyserPtr analyser = m_curLevel->getAnalyser();
+ if(analyser)
+ analyser->groupStart();
+}
+
+void RtfParser::groupEnd()
+{
+ ASSERT(m_curLevel != NULL);
+ AnalyserPtr analyser = m_curLevel->getAnalyser();
+ if(analyser)
+ analyser->groupEnd();
+
+ LevelHandler::groupEnd();
+}
+
+#define ON_INITIALIZE(cls) \
+ void RtfParser::cls::initialize()
+#define ON_CONTROLWORD(cls) \
+ void RtfParser::cls::controlWord(const string& cw, int flags, int param)
+#define ON_CHARDATA(cls) \
+ void RtfParser::cls::charData(wstring data)
+#define ON_GROUPSTART(cls) \
+ void RtfParser::cls::groupStart()
+#define ON_GROUPEND(cls) \
+ void RtfParser::cls::groupEnd()
+#define ON_DONE(cls) \
+ void RtfParser::cls::done()
+#define AN_ELEMENT(name) \
+ m_parser->pushElement(m_parser->createElement(name))
+#define AN_POP_ELEMENT() \
+ m_parser->popElement()
+#define AN_ATTRIBUTE(name, value) \
+ m_parser->setAttribute(name, value)
+#define AN_DESTINATION_ATTR(name) \
+ m_parser->setDestination(new Attribute(name))
+#define AN_DESTINATION(cls) \
+ m_parser->setDestination(new cls)
+#define AN_ANALYSER(cls) \
+ m_parser->setAnalyser(AnalyserPtr(new cls))
+#define AN_SET_ANALYSER(cls) \
+ m_parser->setAnalyser(AnalyserPtr(cls))
+#define HAS_PARAM (flags & kHasParam)
+#define DEFAULT_CONTROLWORD processDefault(cw, flags, param)
+#define DUMMY 1 == 1
+#define NUM_ATTR(n) m_parser->formatInt(n)
+
+bool RtfParser::ParseAnalyser::processDefault(const string& cw, int flags, int param)
+{
+ if(cw == "upr")
+ {
+ AnalyserPtr analy = m_parser->getAnalyser();
+ ASSERT(analy != NULL);
+ AN_SET_ANALYSER(new Upr(analy));
+ return true;
+ }
+
+ return false;
+}
+
+void RtfParser::ParseAnalyser::applyParaFormatting(RtfFormatting* format,
+ DOM::Element& el)
+{
+ if(format == NULL)
+ format = &(m_parser->getTextFormatting());
+
+ wstring fix = kValPara;
+
+ int list = format->paraList();
+ if(list != -1)
+ {
+ el.setAttribute(kAtList, NUM_ATTR(list));
+ }
+ else
+ {
+ el.removeAttribute(kAtList);
+ }
+
+ if(format->paraInTable())
+ el.setAttribute(kAtCell, L"1");
+ else
+ el.removeAttribute(kAtCell);
+
+ int style = format->paraStyle();
+ if(style != -1)
+ el.setAttribute(kElStyle, NUM_ATTR(style));
+ else
+ el.removeAttribute(kElStyle);
+
+ el.setAttribute(kAtFix, fix);
+}
+
+DOM::Element RtfParser::ParseAnalyser::getCurrentBlock()
+{
+ DOM::Node node = m_parser->getElement();
+
+ if(node.hasChildNodes())
+ node = node.getLastChild();
+
+ return m_parser->getPriorElement(node, kElBlock);
+
+}
+
+bool RtfParser::ParseAnalyser::processTextContent(const string& cw, int flags, int param)
+{
+ DOM::Element el;
+ bool process = false;
+
+ RtfFormatting& format = m_parser->getTextFormatting();
+
+ if(cw == "par")
+ {
+ el = getCurrentBlock();
+ if(el != NULL)
+ applyParaFormatting(&format, el);
+
+ el = m_parser->createElement(kElBlock);
+ applyParaFormatting(&format, el);
+ }
+
+ else if(cw == "intbl")
+ format.paraSetTable(true);
+
+ else if(cw == "cell")
+ {
+ el = getCurrentBlock();
+ if(el != NULL)
+ applyParaFormatting(&format, el);
+
+ el = m_parser->createElement(kElCell);
+ m_parser->pushElement(el);
+ m_parser->popElement();
+ el = m_parser->createElement(kElBlock);
+ applyParaFormatting(&format, el);
+ }
+
+ else if(cw == "trowd")
+ el = m_parser->createElement(kElRow);
+
+ else if(cw == "tab")
+ el = m_parser->createElement(kElTab);
+
+ else if(cw == "sect")
+ el = m_parser->createElement(kElSect);
+
+ else if(cw == "page")
+ el = m_parser->createElement(kElPage);
+
+ else if(cw == "s" && HAS_PARAM)
+ format.paraSetStyle(param);
+
+ else if(cw == "line")
+ el = m_parser->createElement(kElLine);
+
+ else if(cw == "header")
+ AN_ANALYSER(Skip);
+ else if(cw == "footer")
+ AN_ANALYSER(Skip);
+ else if(cw == "bkmkstart")
+ AN_ANALYSER(Skip);
+ else if(cw == "listtext")
+ AN_ANALYSER(Skip);
+
+ 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_parser->getDestination();
+ ASSERT(dest != NULL);
+ dest->charData(kValNull);
+
+ m_parser->pushElement(el);
+ m_parser->popElement();
+ }
+
+ return (el != NULL) || process;
+
+ /* TODO: cell, row, intbl, cellx, trowd*/
+}
+
+bool RtfParser::ParseAnalyser::processTextFormatting(const string& cw, int flags,
+ int param, RtfFormatting& format)
+{
+ bool on = true;
+ if(flags & HAS_PARAM && param == 0)
+ on = false;
+
+ if(cw == "pard")
+ {
+ format.resetPara();
+// applyParaFormatting();
+ }
+ 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 == "cf" && HAS_PARAM)
+ format.textSetColor(param);
+ else
+ return false;
+
+ return true;
+}
+
+bool RtfParser::ParseAnalyser::processTextFormatting(const string& cw, int flags, int param)
+{
+ return processTextFormatting(cw, flags, param, m_parser->getTextFormatting());
+}
+
+
+ON_INITIALIZE(Skip)
+ { AN_DESTINATION(Null); }
+ON_GROUPSTART(Skip)
+ { AN_ANALYSER(Skip); }
+
+
+RtfParser::Upr::Upr(AnalyserPtr prv)
+{
+ ASSERT(prv);
+ prev = prv;
+}
+ON_GROUPSTART(Upr)
+ { AN_ANALYSER(Skip); }
+ON_GROUPEND(Upr)
+{
+ ASSERT(prev);
+ m_parser->setAnalyser(prev);
+ prev = NULL;
+}
+
+
+ON_INITIALIZE(Stylesheet)
+{
+ AN_ELEMENT(kElStylesheet);
+}
+ON_GROUPSTART(Stylesheet)
+{
+ AN_ANALYSER(Style);
+ AN_DESTINATION(Null);
+}
+
+
+
+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;
+ }
+
+ if(!haveStyle)
+ {
+ AN_ELEMENT(kElStyle);
+ AN_DESTINATION_ATTR(kAtName);
+ haveStyle = true;
+ }
+
+ if(cw == "s" && flags & kHasParam)
+ {
+ AN_ATTRIBUTE(kAtId, NUM_ATTR(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_parser->getTextFormatting();
+ if(props.textIsBold())
+ AN_ATTRIBUTE(kAtBold, L"1");
+ if(props.textIsHidden())
+ AN_ATTRIBUTE(kAtHidden, L"1");
+ if(props.textIsItalic())
+ AN_ATTRIBUTE(kAtItalic, L"1");
+ if(props.textIsStrike())
+ AN_ATTRIBUTE(kAtStrike, L"1");
+ if(props.textIsUnderline())
+ AN_ATTRIBUTE(kAtUnderline, L"1");
+ if(props.textColor() != -1 && m_parser->getOptions().doColors)
+ AN_ATTRIBUTE(kAtColor, NUM_ATTR(props.textColor()));
+}
+
+
+
+ON_INITIALIZE(ListTable)
+{
+ AN_ELEMENT(kElListtable);
+}
+ON_GROUPSTART(ListTable)
+{
+ AN_ANALYSER(List);
+ AN_DESTINATION(Null);
+}
+
+
+
+ON_INITIALIZE(List)
+{
+ AN_ELEMENT(kElListdef);
+ AN_ATTRIBUTE(kAtType, kValDisc);
+ AN_ATTRIBUTE(kAtOrdered, L"0");
+ levelsSeen = 0;
+}
+ON_CONTROLWORD(List)
+{
+ if(cw == "listname")
+ AN_DESTINATION_ATTR(kAtName);
+ else if(cw == "listid" && HAS_PARAM)
+ AN_ATTRIBUTE(kAtId, NUM_ATTR(param));
+
+ // We let listlevel in here too
+ else if(cw == "levelstartat" && HAS_PARAM)
+ AN_ATTRIBUTE(kAtStart, NUM_ATTR(param));
+
+ else if(cw == "levelnfc" && HAS_PARAM)
+ {
+ 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)
+{
+ if(levelsSeen > 0)
+ AN_ANALYSER(Skip);
+ levelsSeen++;
+}
+
+
+
+
+ON_INITIALIZE(ListOverrideTable)
+{
+ DOM::Document document = m_parser->getDocument();
+ lists = document.getElementsByTagName(kElListdef);
+ curList = NULL;
+ lsId = -1;
+}
+ON_GROUPSTART(ListOverrideTable)
+{
+ 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 = NUM_ATTR(param);
+
+ if(lists != NULL)
+ {
+ 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 = (DOM::Element&)curList.getParentNode();
+ if(parent != NULL)
+ {
+ curList = (DOM::Element&)curList.cloneNode(true);
+ if(curList != NULL)
+ {
+ parent.appendChild(curList);
+ curList.setAttribute(kAtList, NUM_ATTR(lsId));
+ }
+ }
+
+ lsId = -1;
+ }
+
+}
+ON_GROUPEND(ListOverrideTable)
+{
+
+}
+
+
+
+
+ON_INITIALIZE(Info)
+{
+ // Create a new element
+ AN_ELEMENT(kElInfo);
+ AN_DESTINATION(Null);
+}
+ON_CONTROLWORD(Info)
+{
+ // The title
+ 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;
+}
+
+
+
+
+ON_INITIALIZE(Root)
+{
+
+}
+ON_CONTROLWORD(Root)
+{
+ 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")
+ AN_ANALYSER(Skip);
+ else if(cw == "colortbl")
+ AN_ANALYSER(Skip);
+ 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(processTextFormatting(cw, flags, param))
+ DUMMY;
+ else
+ DEFAULT_CONTROLWORD;
+}
+
+
+ON_INITIALIZE(Content)
+{
+ parent = m_parser->getElement();
+ created = false;
+}
+ON_CHARDATA(Content)
+{
+ // Create the first time we get content
+ if(!created)
+ {
+ DOM::Element dest = m_parser->createElement(kElDest);
+ parent.appendChild(dest);
+ m_parser->replaceElement(dest);
+
+ DOM::Element el = m_parser->createElement(kElBlock);
+ m_parser->pushElement(el);
+ m_parser->popElement();
+
+ created = true;
+ }
+
+ if(data.length() == 0)
+ return;
+
+ int elements = 0;
+ RtfFormatting& format = m_parser->getTextFormatting();
+
+ // 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.textColor() != -1 && m_parser->getOptions().doColors)
+ {
+ AN_ELEMENT(kElColor);
+ AN_ATTRIBUTE(kAtIndex, NUM_ATTR(format.textColor()));
+ elements++;
+ }
+
+ // Write the data to the element
+ m_parser->getElement().appendChild(
+ m_parser->getDocument().createTextNode(data));
+
+ // Now drop out of all the above formatting
+ while(elements-- > 0)
+ AN_POP_ELEMENT();
+}
+
+#if 0
+ON_INITIALIZE(Table)
+{
+ stack = 0;
+ level = m_parser->getLevel();
+ AN_ELEMENT(kElTable);
+ AN_DESTINATION(Content);
+}
+
+ON_CONTROLWORD(Table)
+{
+ ASSERT(stack >= 0);
+ ASSERT(level != NULL);
+
+ if(cw == "trowd")
+ {
+ stack++;
+ }
+ else if(cw == "row")
+ {
+ stack--;
+ if(stack <= 0)
+ m_parser->rewindLevel(level);
+ }
+
+ else if(processTextContent(cw, flags, param))
+ DUMMY;
+ else if(processTextFormatting(cw, flags, param))
+ DUMMY;
+ else
+ DEFAULT_CONTROLWORD;
+
+ if(!m_parser->getTextFormatting().paraInTable())
+ {
+ m_parser->rewindLevel(level);
+ }
+
+}
+#endif
+
+
+
+
+ON_CHARDATA(Raw)
+{
+ // Write the data to the element
+ m_parser->getElement().appendChild(
+ m_parser->getDocument().createTextNode(data));
+}
+
+
+
+
+ON_INITIALIZE(Attribute)
+{
+ element = m_parser->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);
+}
+
+
+
+
+
+/**
+ * A quick check to see if a node is an element of a certain
+ * name
+ */
+bool RtfParser::isElement(const DOM::Node& node, const string& name)
+{
+ return node != NULL && node.getNodeType() == DOM::Node::ELEMENT_NODE &&
+ node.getNodeName() == name;
+}
+
+bool RtfParser::isEqualElement(const DOM::Element& el1, const DOM::Element& el2)
+{
+ if(el1.getNodeName() == el2.getNodeName())
+ return false;
+
+ DOM::NamedNodeMap at1 = el1.getAttributes();
+ DOM::NamedNodeMap at2 = el2.getAttributes();
+
+ if(at1 == NULL && at2 == NULL)
+ return true;
+
+ if(at1 == NULL || at2 == NULL ||
+ at1->getLength() != at2->getLength())
+ return false;
+
+ for(int i = 0; i < at1->getLength(); i++)
+ {
+ DOM::Attr attr1 = (DOM::Attr&)at1->item(0);
+ if(attr1 != NULL)
+ return false;
+
+ DOM::Attr attr2 = (DOM::Attr&)at2->getNamedItem(attr1.getNodeName());
+ if(attr2 != NULL)
+ return false;
+
+ if(attr1.getNodeValue() == attr2.getNodeValue())
+ return false;
+ }
+
+ return true;
+}
+
+wstring RtfParser::formatInt(int num)
+{
+ wstring n;
+ n.format(L"%d", num);
+ return n;
+}
+
+/**
+ * Gets the pertinent ancestor of this node, or returns null
+ * if not found.
+ */
+DOM::Element RtfParser::getContainingElement(const DOM::Node& node, const string& name)
+{
+ DOM::Node n = node;
+
+ while(true)
+ {
+ n = n.getParentNode();
+ if(n == NULL)
+ break;
+
+ if(isElement(n, name))
+ return (DOM::Element&)n;
+ }
+
+ return DOM::Element();
+}
+
+DOM::Element RtfParser::getPriorElement(const DOM::Node& node, const string& name)
+{
+ DOM::Node n = node;
+
+ while(n != NULL)
+ {
+ if(isElement(n, name))
+ return (DOM::Element&)n;
+
+ n = n.getPreviousSibling();
+ }
+
+ DOM::Node parent = node.getParentNode();
+
+ if(parent == NULL)
+ return DOM::Element();
+ else
+ return getPriorElement(parent, name);
+}
+
+bool isNsAttr(const string& name)
+{
+ return strncmp(name.c_str(), kNSPrefix, strlen(kNSPrefix)) ? false : true;
+}
+
+void RtfParser::copyAttributes(const DOM::Element& src, DOM::Element& dest,
+ const char** hideList)
+{
+ // Now get both sets of attributes
+ DOM::NamedNodeMap srcMap = src.getAttributes();
+ DOM::NamedNodeMap destMap = dest.getAttributes();
+
+ if(srcMap == NULL || destMap == NULL)
+ return;
+
+ // And copy them from one to the other
+ for(int j = 0; j < srcMap->getLength(); j++)
+ {
+ DOM::Node attr = srcMap->item(j);
+ if(attr != NULL)
+ {
+ // BUG: Sablotron seems to have a bug in it's
+ // setAttributeNode implementation. It always
+ // adds a blank namespace
+ // attr = attr.cloneNode(false);
+ // if(attr != NULL)
+ // destMap.setNamedItem(attr);
+
+ string name = attr.getNodeName();
+
+ if(hideList)
+ {
+
+ for(const char** t = hideList; *t != NULL; t++)
+ {
+ if(name == *t)
+ name.clear();
+ }
+ }
+
+ if(name.length() > 0 && !isNsAttr(name))
+ dest.setAttribute(attr.getNodeName(), attr.getNodeValue());
+ }
+ }
+}
+
+
+void RtfParser::breakBreak(DOM::Document& doc, const string& contain,
+ const string& tag)
+{
+ DOM::NodeList els = doc.getElementsByTagName(tag);
+ if(els != NULL)
+ {
+ for(int i = 0; i < els->getLength(); i++)
+ {
+ DOM::Element el = (DOM::Element&)els->item(i);
+#if 0
+ // See if parent node only has this break tag
+ // in it. If so then replace parent with this
+
+ DOM::Node parent = el.getParentNode();
+
+ if(parent != NULL)
+ {
+ DOM::Node grandparent = parent.getParentNode();
+
+ if(grandparent != NULL &&
+ el.getPreviousSibling() == NULL &&
+ el.getNextSibling() == NULL)
+ {
+ grandparent.replaceChild(parent.removeChild(el), parent);
+ }
+ }
+#endif
+
+ breakElement(el, contain);
+ }
+ }
+}
+
+/**
+ * Breaks a paragraph up through a previous level. Calls itself
+ * recursively to break paragraphs totally free up to containing
+ * destination.
+ *
+ * For example:
+ *
+ * <dest>
+ * This is <b> a <block fix="para"/>
+ * test of </b> your concentration.
+ * </dest>
+ *
+ * Becomes:
+ *
+ * <dest>
+ * This is <b> a </b><block fix="para"/>
+ * <b>test of </b> your concentration.
+ * </dest>
+ */
+bool RtfParser::breakElement(DOM::Element& el, const string& contain)
+{
+ ASSERT(el != NULL);
+
+ DOM::Element parent = (DOM::Element&)el.getParentNode();
+ DOM::Element grandparent;
+
+ string s = el.getNodeName();
+ s = parent.getNodeName();
+
+ // Get the parent node
+ if(parent != NULL)
+ grandparent = (DOM::Element&)parent.getParentNode();
+
+ // Make sure we have something to work with before continuing
+ if(grandparent == NULL || parent == NULL ||
+ isElement(parent, contain))
+ return true;
+
+ DOM::Node e;
+
+ // Check to see if this is the first node in the parent.
+ // If so then just move out to before
+ if(el.getPreviousSibling() == NULL)
+ {
+ e = grandparent.insertBefore(parent.removeChild(el), parent);
+ }
+
+
+ // Check to see if this is the last node in the parent.
+ // If so then just move out to after the parent
+ else if(el.getNextSibling() == NULL)
+ {
+ DOM::Node next = parent.getNextSibling();
+ if(next == NULL)
+ e = grandparent.appendChild(parent.removeChild(el));
+ else
+ e = grandparent.insertBefore(parent.removeChild(el), next);
+ }
+
+
+ // Otherwise it's in the middle so split the parent
+ // element etc...
+ else
+ {
+ // Clone it but not deep
+ DOM::Element parent2 = (DOM::Element&)parent.cloneNode(false);
+
+ if(parent2 == NULL)
+ return false;
+
+ // Flag that tells us whether we moved anything up to parent
+ bool moved = false;
+
+ // Now move all nodes after this one to the second parent.
+ while((e = el.getNextSibling()) != NULL)
+ {
+ parent2.appendChild(parent.removeChild(e));
+ moved = true;
+ }
+
+ // Remove the element from it's parent
+ e = parent.removeChild(el);
+
+ // Okay now we move the paragraph up to the parent
+ insertAfter(grandparent, e, parent);
+ if(moved)
+ insertAfter(grandparent, parent2, e);
+ }
+
+ // Now call it again with the paragraph in the new position
+ // untill everything's cut through!
+ return breakElement((DOM::Element&)e, contain);
+}
+
+/**
+ * Changes from a marker based paragraph system to a contained
+ * paragraph system. Also applies paragraph attributes to the
+ * appropriate paragraph.
+ *
+ * For example:
+ *
+ * <dest>
+ * This <blockattr style="10"> is <b> a <block fix="para"/>
+ * test of </b> your concentration.
+ * </dest>
+ *
+ * Becomes:
+ *
+ * <para style="10"> This is <b> a </b></para>
+ * <para><b>test of </b> your concentration.</para>
+ */
+void RtfParser::breakBlocks(DOM::Document& document)
+{
+ // First break out all the paragraphs to the destination level
+ DOM::NodeList blocks = document.getElementsByTagName(kElBlock);
+ if(blocks != NULL)
+ {
+ for(int i = 0; i < blocks->getLength(); i++)
+ {
+ DOM::Element block = (DOM::Element&)blocks->item(i);
+
+ // If it's the single closed style para then break it
+ if(block != NULL && !block.hasChildNodes())
+ breakElement(block, kElDest);
+ }
+ }
+
+
+ // Now group stuff in destinations into paras or other blocks
+ DOM::NodeList destinations = document.getElementsByTagName(kElDest);
+ if(destinations != NULL)
+ {
+ for(int i = 0; i < destinations->getLength(); i++)
+ {
+ DOM::Element dest = (DOM::Element&)destinations->item(i);
+
+ // Sanity Check
+ if(dest == NULL || !dest.hasChildNodes())
+ continue;
+
+ // Go through the children of this destination
+ DOM::Node child = dest.getFirstChild();
+
+ DOM::Element block;
+
+ while(child != NULL)
+ {
+ // If it's a block
+ if(isElement(child, kElBlock))
+ {
+ block = (DOM::Element&)child;
+ child = child.getNextSibling();
+ continue;
+ }
+
+ // If it's already a real block element
+ for(const char** t = kBlockTags; *t != NULL; t++)
+ {
+ if(isElement(child, *t))
+ {
+ block = NULL;
+ break;
+ }
+ }
+
+ // If there's a block then add to it
+ if(block != NULL)
+ {
+ block.appendChild(dest.removeChild(child));
+ child = block;
+ }
+
+ child = child.getNextSibling();
+ }
+ }
+ }
+}
+
+void RtfParser::wrapTags(DOM::Document& doc, const string& tagName,
+ const string& wrapName)
+{
+ DOM::NodeList tags = doc.getElementsByTagName(tagName);
+ if(tags != NULL)
+ {
+ for(int i = 0; i < tags->getLength(); i++)
+ {
+ DOM::Element tag = (DOM::Element&)tags->item(i);
+
+ DOM::Element wrap = doc.createElement(wrapName);
+ while(tag.hasChildNodes())
+ wrap.appendChild(tag.removeChild(tag.getFirstChild()));
+
+ tag.appendChild(wrap);
+ }
+ }
+}
+
+void RtfParser::breakTags(DOM::Document& doc, const string& parentName,
+ const string& tagName)
+{
+ DOM::NodeList parents = doc.getElementsByTagName(parentName);
+ if(parents != NULL)
+ {
+ for(int i = 0; i < parents->getLength(); i++)
+ {
+ DOM::Element parent = (DOM::Element&)parents->item(i);
+
+ if(!parent.hasChildNodes())
+ continue;
+
+ DOM::NodeList tags = parent.getElementsByTagName(tagName);
+ if(tags != NULL)
+ {
+ for(int i = 0; i < tags->getLength(); i++)
+ breakElement((DOM::Element&)tags->item(i), parentName);
+ }
+
+ DOM::Node tag = doc.createElement(tagName);
+ parent.insertBefore(tag, parent.getFirstChild());
+
+ DOM::Node child = tag;
+
+ while(child != NULL && (child = child.getNextSibling()) != NULL)
+ {
+ if(isElement(child, kElBlock))
+ {
+ DOM::Node next = child.getNextSibling();
+ if(next == NULL)
+ {
+ parent.removeChild(child);
+ continue;
+ }
+
+ if(isElement(next, tagName))
+ {
+ DOM::Node twodown = next.getNextSibling();
+ if(!isElement(twodown, kElBlock))
+ {
+ child = parent.insertBefore(parent.removeChild(next), child);
+ }
+ else
+ {
+ parent.removeChild(child);
+ child = next;
+ }
+ }
+ }
+
+ if(isElement(child, tagName))
+ {
+ if(!tag.hasChildNodes())
+ parent.removeChild(tag);
+ tag = child;
+ }
+ else
+ {
+ tag.appendChild(parent.removeChild(child));
+ child = tag;
+ }
+ }
+
+ if(!tag.hasChildNodes())
+ parent.removeChild(tag);
+ }
+ }
+
+ DOM::NodeList tags = doc.getElementsByTagName(tagName);
+ if(tags != NULL)
+ {
+ for(int i = 0; i < tags->getLength(); i++)
+ {
+ DOM::Element tag = (DOM::Element&)tags->item(i);
+ DOM::Node parent = tag.getParentNode();
+
+ if(parent != NULL && !isElement(parent, parentName))
+ parent.removeChild(tag);
+
+#if 0
+ else if(tag.hasChildNodes())
+ {
+ DOM::NodeList children = tag.getChildNodes();
+ if(children != NULL && children->getLength() == 1)
+ {
+ DOM::Node child = children->item(0);
+ if(child != NULL && !child.hasChildNodes() &&
+ isElement(child, kElBlock))
+ parent.removeChild(tag);
+ }
+ }
+#endif
+
+ }
+ }
+}
+
+void RtfParser::breakLists(DOM::Document& doc)
+{
+ // Now group stuff in destinations into tables
+ DOM::NodeList destinations = doc.getElementsByTagName(kElDest);
+ if(destinations != NULL)
+ {
+ for(int i = 0; i < destinations->getLength(); i++)
+ {
+ DOM::Element dest = (DOM::Element&)destinations->item(i);
+
+ // Sanity Check
+ if(dest == NULL)
+ continue;
+
+ // Go through the children of this destination
+ DOM::Node child = dest.getFirstChild();
+
+ DOM::Element list;
+ DOM::Element e;
+
+ wstring previd;
+
+ while(child != NULL)
+ {
+ // If it's a block and has a cell attribute
+ if(isElement(child, kElBlock))
+ {
+ e = (DOM::Element&)child;
+
+ // if it has a cell attribute
+ wstring listid = e.getAttribute(kAtList);
+ if(listid.length() > 0)
+ {
+ e.removeAttribute(kAtList);
+
+ if(list == NULL || previd != listid)
+ {
+ list = doc.createElement(kElList);
+ list.setAttribute(kAtList, listid);
+ dest.insertBefore(list, child);
+ previd = listid;
+ }
+ }
+ else
+ {
+ list = NULL;
+ previd.clear();
+ }
+ }
+
+ // It's not a block
+ if(list != NULL)
+ {
+ list.appendChild(dest.removeChild(child));
+ child = list;
+ }
+
+ child = child.getNextSibling();
+ }
+ }
+ }
+}
+
+void RtfParser::fixStyles(const DOM::Document doc)
+{
+ DOM::NodeList styles = doc.getElementsByTagName(kElStyle);
+ if(styles != NULL)
+ {
+ DOM::NodeList blocks = doc.getElementsByTagName(kElBlock);
+ if(blocks != NULL)
+ {
+ for(int i = 0; i < blocks->getLength(); i++)
+ {
+ DOM::Element block = (DOM::Element&)blocks->item(i);
+
+ if(block == NULL || !block.hasAttribute(kElStyle))
+ continue;
+
+ for(int j = 0; j < styles->getLength(); j++)
+ {
+ DOM::Element style = (DOM::Element&)styles->item(j);
+ if(style != NULL)
+ {
+ if(style.getAttribute(kAtId) == block.getAttribute(kElStyle))
+ {
+ wstring name = style.getAttribute(kAtName);
+ if(name.length() > 0)
+ block.setAttribute(kElStyle, name);
+ }
+ }
+ }
+ }
+ }
+
+ for(int i = 0; i < styles->getLength(); i++)
+ {
+ DOM::Element style = (DOM::Element&)styles->item(i);
+ if(style != NULL)
+ style.removeAttribute(kAtId);
+ }
+ }
+
+
+}
+
+
+void RtfParser::breakTables(DOM::Document& doc)
+{
+ DOM::NodeList rows = doc.getElementsByTagName(kElRow);
+ if(rows != NULL)
+ {
+ for(int i = 0; i < rows->getLength(); i++)
+ {
+ DOM::Element row = (DOM::Element&)rows->item(i);
+ DOM::Node parent = row.getParentNode();
+
+ if(parent == NULL)
+ continue;
+
+ if(isElement(parent, kElBlock))
+ {
+ DOM::Node grandparent = parent.getParentNode();
+
+ if(grandparent != NULL && !row.hasChildNodes())
+ {
+ if(row.getPreviousSibling() == NULL)
+ grandparent.insertBefore(parent.removeChild(row), parent);
+ else if(row.getNextSibling() == NULL)
+ insertAfter(grandparent, parent.removeChild(row), parent);
+ }
+ }
+
+ breakElement(row, kElDest);
+ }
+ }
+
+
+
+ // Now group stuff in destinations into tables
+ DOM::NodeList destinations = doc.getElementsByTagName(kElDest);
+ if(destinations != NULL)
+ {
+ for(int i = 0; i < destinations->getLength(); i++)
+ {
+ DOM::Element dest = (DOM::Element&)destinations->item(i);
+
+ // Sanity Check
+ if(dest == NULL)
+ continue;
+
+ // Go through the children of this destination
+ DOM::Node child = dest.getFirstChild();
+
+ DOM::Element table;
+ DOM::Element e;
+
+ while(child != NULL)
+ {
+ // If it's a block and has a cell attribute
+ if(isElement(child, kElBlock))
+ {
+ e = (DOM::Element&)child;
+
+ // if it has a cell attribute
+ if(e.getAttribute(kAtCell).length() > 0)
+ {
+ e.removeAttribute(kAtCell);
+
+ if(table == NULL)
+ {
+ table = doc.createElement(kElTable);
+ dest.insertBefore(table, child);
+ }
+ }
+ else
+ {
+ table = NULL;
+ }
+ }
+
+ // It's not a block
+ if(table != NULL)
+ {
+ table.appendChild(dest.removeChild(child));
+ child = table;
+ }
+
+ child = child.getNextSibling();
+ }
+ }
+ }
+}
+
+void RtfParser::insertAfter(DOM::Node& parent, const DOM::Node& node,
+ const DOM::Node& ref)
+{
+ DOM::Node sibling = ref.getNextSibling();
+ if(sibling == NULL)
+ parent.appendChild(node);
+ else
+ parent.insertBefore(node, sibling);
+}
+
+void RtfParser::removeTags(const DOM::Document& doc)
+{
+ // Go through the list of nodes
+ for(const char** t = kRemoveTags; *t != NULL; t++)
+ {
+ DOM::NodeList elements = doc.getElementsByTagName(*t);
+ if(elements != NULL)
+ {
+ for(int j = 0; j < elements->getLength(); j++)
+ {
+ DOM::Element el = (DOM::Element&)elements->item(j);
+ DOM::Node parent = el->getParentNode();
+
+ if(parent == NULL)
+ continue;
+
+ while(el.hasChildNodes())
+ parent.insertBefore(el.removeChild(el.getFirstChild()), el);
+
+ parent.removeChild(el);
+ }
+ }
+ }
+}
+
+void RtfParser::fixLists(const DOM::Document doc)
+{
+ DOM::NodeList lists = doc.getElementsByTagName(kElList);
+ if(lists != NULL)
+ {
+ DOM::NodeList listdefs = doc.getElementsByTagName(kElListdef);
+ if(listdefs != NULL)
+ {
+ for(int i = 0; i < listdefs->getLength(); i++)
+ {
+ DOM::Element listdef = (DOM::Element&)listdefs->item(i);
+
+ if(listdef == NULL || !listdef.hasAttribute(kAtList))
+ continue;
+
+ for(int j = 0; j < lists->getLength(); j++)
+ {
+ DOM::Element list = (DOM::Element&)lists->item(j);
+ if(list != NULL)
+ {
+ if(list.getAttribute(kAtList) == listdef.getAttribute(kAtList))
+ {
+ copyAttributes(listdef, list, kHideList);
+ list.removeAttribute(kAtList);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void RtfParser::fixBlocks(const DOM::Document doc)
+{
+ // First break out all the paragraphs to the destination level
+ DOM::NodeList blocks = doc.getElementsByTagName(kElBlock);
+ if(blocks != NULL)
+ {
+ string fix;
+ wstring val;
+
+ for(int i = 0; i < blocks->getLength(); i++)
+ {
+ DOM::Element block = (DOM::Element&)blocks->item(i);
+ DOM::Node parent = block.getParentNode();
+
+ if(parent == NULL)
+ continue;
+
+ fix.resize(0);
+ val.resize(0);
+
+ val = block.getAttribute(kAtFix);
+ if(val.length() > 0)
+ block.removeAttribute(kAtFix);
+
+
+ if(val.length() > 0)
+ {
+ val = block.getAttributeNS("", kAtFix);
+ if(val.length() > 0)
+ block.removeAttributeNS("", kAtFix);
+ }
+
+ if(val.length() > 0)
+ DOM::transcode16to8(val, fix);
+
+ if(fix.length() == 0)
+ fix = kElPara;
+
+ DOM::Element el = doc.createElement(fix);
+ copyAttributes(block, el, NULL);
+
+ while(block.hasChildNodes())
+ el.appendChild(block.removeChild(block.getFirstChild()));
+
+ parent.replaceChild(el, block);
+ }
+ }
+}
+
+
+/**
+ * Removes adjacent duplicate nodes of certain names
+ */
+void RtfParser::removeDuplicates(const DOM::Document& doc)
+{
+ // Go through the list of nodes
+ for(const char** t = kNoDuplicates; *t = NULL; t++)
+ {
+ DOM::NodeList elements = doc.getElementsByTagName(*t);
+ if(elements != NULL)
+ {
+ int x = elements->getLength();
+ for(int j = 0; j < elements->getLength(); j++)
+ {
+
+ // Make sure it's a valid element
+ DOM::Element element = (DOM::Element&)elements->item(j);
+ if(element == NULL)
+ continue;
+
+ // Get neighbors
+ DOM::Node previous = element.getPreviousSibling();
+ DOM::Node next = element.getNextSibling();
+
+ // Make sure it's still in the document, as we may have
+ // removed it on a previous loop
+ DOM::Node parent = element.getParentNode();
+ if(parent == NULL)
+ continue;
+
+ // Combine previous if valid
+ if(previous != NULL && previous.getNodeType() == DOM::Node::ELEMENT_NODE &&
+ isEqualElement((DOM::Element&)previous, element))
+ {
+ while(previous.hasChildNodes())
+ {
+ DOM::Node child = previous.removeChild(previous.getLastChild());
+ if(child != NULL)
+ {
+ if(element.hasChildNodes())
+ element.insertBefore(child, element.getFirstChild());
+ else
+ element.appendChild(child);
+ }
+ }
+
+ // Remove duplicate node
+ parent.removeChild(previous);
+ }
+
+ // Combine next if valid
+ if(next != NULL && next.getNodeType() == DOM::Node::ELEMENT_NODE &&
+ isEqualElement((DOM::Element&)next, element))
+ {
+ while(next.hasChildNodes())
+ {
+ DOM::Node child = next.removeChild(next.getFirstChild());
+ if(child != NULL)
+ element.appendChild(child);
+ }
+
+ // Remove duplicate node
+ parent.removeChild(next);
+ }
+ }
+ }
+ }
+} \ No newline at end of file