/*************************************************************************** * Copyright (C) 2004 by Tomas Mecir * * kmuddy@kmuddy.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * ***************************************************************************/ #include "cmxpstate.h" #include "centitymanager.h" #include "cmxpcolors.h" #include "cresulthandler.h" #include "rgbops.h" #include "stringops.h" #include #include #include cMXPState::cMXPState (cResultHandler *resh, cElementManager *elm, cEntityManager *enm) { results = resh; elements = elm; entities = enm; //currently implemented MXP version mxpVersion = "1.0"; //starting MXP mode is LOCKED, to prevent problems with non-MXP MUDs). //This goes against the MXP protocol, therefore there's a setting that will keep the OPEN //mode, if desired - see public API. mode = lockedMode; defaultmode = lockedMode; initiallyLocked = true; tempMode = false; wasSecureMode = false; //some default values... cMXPColors *colors = cMXPColors::self(); defaultfg = colors->color ("gray"); defaultbg = colors->color ("black"); defaultfont = "Courier"; defaultsize = 12; defaultattribs = 0; //by default, all headers are written in the same font (Courier), they are bold and they //differ in sizes... for (int i = 0; i < 6; i++) { Hfont[i] = "Courier"; Hfg[i] = defaultfg; Hbg[i] = defaultbg; Hattribs[i] = Bold; } Hsize[0] = 32; Hsize[1] = 24; Hsize[2] = 20; Hsize[3] = 16; Hsize[4] = 14; Hsize[5] = 12; ttFont = "Courier"; setDefaultGaugeColor (colors->color ("white")); //PACKAGE and VERSION are defined in config.h clientName = PACKAGE; clientVersion = VERSION; //some default screen and font attributes... fX = 16; fY = 8; sX = 800; sY = 600; suplink = supgauge = supstatus = supframe = supimage = suprelocate = false; //params reset (); } cMXPState::~cMXPState () { //delete mxpResult structures in closing tags list list::iterator it; for (it = closingTags.begin(); it != closingTags.end(); ++it) { if ((*it)->closingresult) delete (*it)->closingresult; list *rlist = (*it)->closingresults; if (rlist) { list::iterator it2; for (it2 = rlist->begin(); it2 != rlist->end(); ++it2) delete *it2; delete rlist; } } closingTags.clear (); } //some user-adjustable parameters void cMXPState::setDefaultText (const string &font, int size, bool _bold, bool _italic, bool _underline, bool _strikeout, RGB fg, RGB bg) { if (curfont == defaultfont) curfont = font; defaultfont = font; if (cursize == defaultsize) cursize = size; defaultsize = size; char curattrib = (bold?1:0) * Bold + (italic?1:0) * Italic + (underline?1:0) * Underline + (strikeout?1:0) * Strikeout; char newattribs = (_bold?1:0) * Bold + (_italic?1:0) * Italic + (_underline?1:0) * Underline + (_strikeout?1:0) * Strikeout; if (curattrib == defaultattribs) { bold = _bold; italic = _italic; underline = _underline; strikeout = _strikeout; } defaultattribs = newattribs; if (fgcolor == defaultfg) fgcolor = fg; defaultfg = fg; if (bgcolor == defaultbg) bgcolor = bg; defaultbg = bg; } void cMXPState::setHeaderParams (int which, const string &font, int size, bool _bold, bool _italic, bool _underline, bool _strikeout, RGB fg, RGB bg) { //invalid H-num? if ((which < 1) || (which > 6)) return; Hfont[which - 1] = font; Hsize[which - 1] = size; char newattribs = (_bold?1:0) * Bold + (_italic?1:0) * Italic + (_underline?1:0) * Underline + (_strikeout?1:0) * Strikeout; Hattribs[which - 1] = newattribs; Hfg[which - 1] = fg; Hbg[which - 1] = bg; } void cMXPState::setDefaultGaugeColor (RGB color) { gaugeColor = color; } void cMXPState::setNonProportFont (string font) { ttFont = font; } void cMXPState::setClient (string name, string version) { clientName = name; clientVersion = version; } void cMXPState::supportsLink (bool supports) { suplink = supports; } void cMXPState::supportsGauge (bool supports) { supgauge = supports; } void cMXPState::supportsStatus (bool supports) { supstatus = supports; } void cMXPState::supportsSound (bool supports) { supsound = supports; } void cMXPState::supportsFrame (bool supports) { supframe = supports; } void cMXPState::supportsImage (bool supports) { supimage = supports; } void cMXPState::supportsRelocate (bool supports) { suprelocate = supports; } void cMXPState::switchToOpen () { mode = openMode; defaultmode = openMode; initiallyLocked = false; //not we conform to MXP spec... use with care - only affects non-MXP MUDs, where it allows //open tags - MUDs supporting MXP are NOT affected } void cMXPState::reset () { bold = defaultattribs & Bold; italic = defaultattribs & Italic; underline = defaultattribs & Underline; strikeout = defaultattribs & Strikeout; fgcolor = defaultfg; bgcolor = defaultbg; curfont = defaultfont; cursize = defaultsize; inVar = false; varValue = ""; inParagraph = false; ignoreNextNewLine = false; inLink = false; isALink = false; linkText = ""; gotmap = false; curWindow = ""; prevWindow = ""; } //modes, mode switching mxpMode cMXPState::getMXPMode () { return mode; } void cMXPState::setMXPMode (mxpMode m) { mode = m; tempMode = false; wasSecureMode = false; //if we start in LOCKED mode and mode change occurs, we set default mode //to OPEN, so that we are compatible with the spec... if (initiallyLocked) { initiallyLocked = false; defaultmode = openMode; } } void cMXPState::gotLineTag (int number) { //got a line tag - close outstanding entities, if any (unless we're in LOCKED mode) if (mode != lockedMode) { string t = entities->expandEntities ("", true); if (!t.empty()) gotText (t, false); } //leaving secure mode if (wasSecureMode && (number != 1)) closeAllTags (); wasSecureMode = false; if (number < 0) return; if (number > 99) return; if (number >= 10) results->addToList (results->createLineTag (number)); else { switch (number) { case 0: setMXPMode (openMode); break; case 1: setMXPMode (secureMode); break; case 2: setMXPMode (lockedMode); break; case 3: closeAllTags (); //default mode remains the same... setMXPMode (openMode); reset (); break; case 4: setMXPMode (secureMode); tempMode = true; break; case 5: setMXPMode (openMode); defaultmode = openMode; break; case 6: setMXPMode (secureMode); defaultmode = secureMode; break; case 7: setMXPMode (lockedMode); defaultmode = lockedMode; break; default: results->addToList (results->createWarning ("Received unrecognized line tag.")); break; }; } } void cMXPState::closeAllTags () { if (closingTags.empty()) return; //process open tags one by one... while (!closingTags.empty()) { //closingTags is a FIFO queue, tho technically it's a list closingTag *tag = closingTags.back (); closingTags.pop_back (); results->addToList (results->createWarning ("Had to auto-close tag " + tag->name + ".")); closeTag (tag); } } void cMXPState::commonTagHandler () { //got a new tag - close outstanding entities, if any (unless we're in LOCKED mode) if (mode != lockedMode) { string t = entities->expandEntities ("", true); if (!(t.empty())) gotText (t, false); } //outstanding tags are closed, if we're going out of secure mode, unless a change back to secure //mode occurs if (wasSecureMode) { closeAllTags (); wasSecureMode = false; } //error is reported, if we're inside VAR... if (inVar) results->addToList (results->createError ("Got a tag inside a variable!")); } void cMXPState::commonAfterTagHandler () { //secure mode for one tag? if (tempMode) { tempMode = false; //set mode back to default mode mode = defaultmode; } } //regular text void cMXPState::gotText (const string &text, bool expandentities) { if (text.length() == 0) return; //temp-secure mode -> ERROR! if (tempMode) { tempMode = false; mode = defaultmode; results->addToList (results->createError ("Temp-secure line tag not followed by a tag!")); } //outstanding tags are closed, if we're going out of secure mode, unless a change back to secure //mode occurs if (wasSecureMode) { closeAllTags (); wasSecureMode = false; } //expand entities, if needed string t; if (expandentities && (mode != lockedMode)) t = entities->expandEntities (text, false); else t = text; //special handling if we're in a variable or a link if (inVar) varValue.append (t); if (inLink) linkText.append (t); //text can be sent is it's not a part of a link or of a variable if (!(inVar || inLink)) //add text to the list of things to send results->addToList (results->createText (t)); } void cMXPState::gotNewLine () { //got a newline char - close outstanding entities, if any (unless we're in LOCKED mode) if (mode != lockedMode) { string t = entities->expandEntities ("", true); if (!t.empty()) gotText (t, false); } //was temp-secure mode? if (tempMode) { tempMode = false; mode = defaultmode; results->addToList (results->createError ("Temp-secure line tag followed by a newline!")); } //leaving secure mode? wasSecureMode = false; if ((mode == secureMode) && (defaultmode != secureMode)) wasSecureMode = true; //ending line in OPEN mode - close all tags! if (mode == openMode) closeAllTags (); //is we're in SECURE mode, some tags may need to be closed... //line ended inside a link if (inLink) { inLink = false; isALink = false; linkText = ""; results->addToList (results->createError ("Received an unterminated link!")); } if (inVar) { inVar = false; results->addToList (results->createError ("Received an unterminated VAR tag!")); varValue = ""; } //should next newline be ignored? if (ignoreNextNewLine) { ignoreNextNewLine = false; return; } //if we're in a paragraph, don't report the new-line either if (inParagraph) return; //set mode back to default mode mode = defaultmode; //neither NOBR nor P - report newline results->addToList (results->createText ("\r\n")); } //flags //we treat flag as another tag - this is needed to allow correct flag closing even if the //appropriate closing tag wasn't sent by the MUD (auto-closing of flag) void cMXPState::gotFlag (bool begin, string flag) { bool setFlag = false; //is this a set-variable flag? string f = lcase (flag); if ((f[0] == 's') && (f[1] == 'e') && (f[2] == 't') && (f[3] == ' ')) setFlag = true; //disable inVar and remember old value, if this is a set-flag //this is needed to prevent error report in commonTagHandler() bool oldInVar = inVar; if (setFlag) inVar = false; commonTagHandler(); //restore inVar value inVar = oldInVar; //no -> inform about the flag if (begin) { mxpResult *res = results->createFlag (true, flag); mxpResult *res2 = createClosingResult (res); results->addToList (res); addClosingTag ("flag", res2); //"set xxx" type of flag? if (setFlag) { if (inVar) //in variable already { results->addToList (results->createError ("Got a set-flag, but I'm already in a variable definition!")); return; } //we are now in a variable inVar = true; varName = f.substr (f.rfind (' ') + 1); //last word varValue = ""; } } else { //closing set-flag... if (inVar && setFlag) { results->addToList (results->createVariable (varName, varValue)); //send variable value, but no varname as in results->addToList (results->createText (varValue)); entities->addEntity (varName, varValue); inVar = false; varName = ""; varValue = ""; } gotClosingTag ("flag"); } //no commonAfterTagHandler() here - this ain't no real tag :D } //tags: //variables void cMXPState::gotVariable (const string &name, const string &value, bool erase) { commonTagHandler(); //send the variable value results->addToList (results->createVariable (name, value, erase)); commonAfterTagHandler(); } void cMXPState::gotVAR (const string &name) { commonTagHandler(); if (inVar) { results->addToList (results->createError ("Nested VAR tags are not allowed!")); commonAfterTagHandler(); return; } //we are now in a variable inVar = true; varName = name; varValue = ""; //create a closing result; the variable name shall be updated when the tag will be closed addClosingTag ("var"); commonAfterTagHandler(); } //text formatting (OPEN tags) void cMXPState::gotBOLD () { commonTagHandler(); mxpResult *res = results->createFormatting (USE_BOLD, Bold, cMXPColors::noColor(), cMXPColors::noColor(), "", 0); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("b", res2); commonAfterTagHandler(); } void cMXPState::gotITALIC () { commonTagHandler(); mxpResult *res = results->createFormatting (USE_ITALICS, Italic, cMXPColors::noColor(), cMXPColors::noColor(), "", 0); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("i", res2); commonAfterTagHandler(); } void cMXPState::gotUNDERLINE () { commonTagHandler(); mxpResult *res = results->createFormatting (USE_UNDERLINE, Underline, cMXPColors::noColor(), cMXPColors::noColor(), "", 0); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("u", res2); commonAfterTagHandler(); } void cMXPState::gotSTRIKEOUT () { commonTagHandler(); mxpResult *res = results->createFormatting (USE_STRIKEOUT, Strikeout, cMXPColors::noColor(), cMXPColors::noColor(), "", 0); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("s", res2); commonAfterTagHandler(); } void cMXPState::gotCOLOR (RGB fg, RGB bg) { commonTagHandler(); mxpResult *res = results->createFormatting (USE_FG | USE_BG, 0, fg, bg, "", 0); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("c", res2); commonAfterTagHandler(); } void cMXPState::gotHIGH () { commonTagHandler(); RGB color = fgcolor; //High color is computed by adding 128 to each attribute... //This is a very primitive way of doing it, and it's probably insufficient. We'll see. color.r = (color.r < 128) ? (color.r + 128) : 255; color.g = (color.g < 128) ? (color.g + 128) : 255; color.b = (color.b < 128) ? (color.b + 128) : 255; mxpResult *res = results->createFormatting (USE_FG, 0, color, cMXPColors::noColor(), "", 0); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("h", res2); commonAfterTagHandler(); } void cMXPState::gotFONT (const string &face, int size, RGB fg, RGB bg) { commonTagHandler(); mxpResult *res = results->createFormatting (USE_FG | USE_BG | USE_FONT | USE_SIZE, 0, fg, bg, face, size); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("font", res2); commonAfterTagHandler(); } //line spacing void cMXPState::gotNOBR () { commonTagHandler(); //next new-line is to be ignored ignoreNextNewLine = true; //no reporting to client commonAfterTagHandler(); } void cMXPState::gotP () { commonTagHandler(); //we're now in a paragraph inParagraph = true; addClosingTag ("p"); //no reporting to the client commonAfterTagHandler(); } void cMXPState::gotBR () { commonTagHandler(); //inform the client that we got a newline (but no mode changes shall occur) results->addToList (results->createText ("\r\n")); commonAfterTagHandler(); } void cMXPState::gotSBR () { commonTagHandler(); //soft-break is represented as 0x1F results->addToList (results->createText ("\x1f")); commonAfterTagHandler(); } //links void cMXPState::gotA (const string &href, const string &hint, const string &expire) { commonTagHandler(); inLink = true; isALink = true; linkText = ""; mxpResult *res = results->createLink (expire, href, "", hint); addClosingTag ("a", res); commonAfterTagHandler(); } void cMXPState::gotSEND (const string &command, const string &hint, bool prompt, const string &expire) { commonTagHandler(); inLink = true; isALink = false; linkText = ""; gotmap = false; lastcmd = command; mxpResult *res = results->createSendLink (expire, command, "", hint, prompt, (command.find ("|") == string::npos) ? false : true); addClosingTag ("send", res); commonAfterTagHandler(); } void cMXPState::gotEXPIRE (const string &name) { commonTagHandler(); results->addToList (results->createExpire (name)); commonAfterTagHandler(); } //version control void cMXPState::gotVERSION () { commonTagHandler(); //this is to be sent... results->addToList (results->createSendThis ("\x1b[1z\r\n")); commonAfterTagHandler(); } void cMXPState::gotSUPPORT (list params) { commonTagHandler(); if (!params.empty()) //some parameters - this is not supported at the moment results->addToList (results->createWarning ( "Received with parameters, but this isn't supported yet...")); string res; res = "\x1b[1zaddToList (results->createSendThis (res)); commonAfterTagHandler(); } //optional tags go next //other HTML tags void cMXPState::gotHtag (int which) { if ((which < 1) || (which > 6)) //BUG!!! { commonAfterTagHandler(); return; } commonTagHandler(); int idx = which - 1; mxpResult *res = results->createFormatting (USE_ALL, Hattribs[idx], Hfg[idx], Hbg[idx], Hfont[idx], Hsize[idx]); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); char ct[3]; ct[0] = 'h'; ct[1] = '1' + idx; ct[2] = '\0'; addClosingTag (ct, res2); commonAfterTagHandler(); } void cMXPState::gotHR () { commonTagHandler(); results->addToList (results->createHorizLine ()); commonAfterTagHandler(); } void cMXPState::gotSMALL () { commonTagHandler(); //SMALL means 3/4 of standard size :) mxpResult *res = results->createFormatting (USE_SIZE, 0, cMXPColors::noColor(), cMXPColors::noColor(), "", defaultsize * 3/4); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("small", res2); commonAfterTagHandler(); } void cMXPState::gotTT () { commonTagHandler(); mxpResult *res = results->createFormatting (USE_FONT, 0, cMXPColors::noColor(), cMXPColors::noColor(), ttFont, 0); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); addClosingTag ("tt", res2); commonAfterTagHandler(); } //MSP compatibility void cMXPState::gotSOUND (const string &fname, int vol, int count, int priority, const string &type, const string &url) { commonTagHandler(); results->addToList (results->createSound (true, fname, vol, count, priority, false, type, url)); commonAfterTagHandler(); } void cMXPState::gotMUSIC (const string &fname, int vol, int count, bool contifrereq, const string &type, const string &url) { commonTagHandler(); results->addToList (results->createSound (false, fname, vol, count, 0, contifrereq, type, url)); commonAfterTagHandler(); } //gauges / status bars void cMXPState::gotGAUGE (const string &entity, const string &maxentity, const string &caption, RGB color) { commonTagHandler(); results->addToList (results->createGauge (entity, maxentity, caption, color)); commonAfterTagHandler(); } void cMXPState::gotSTAT (const string &entity, const string &maxentity, const string &caption) { commonTagHandler(); results->addToList (results->createStat (entity, maxentity, caption)); commonAfterTagHandler(); } //frames and cursor control void cMXPState::gotFRAME (const string &name, const string &action, const string &title, bool internal, const string &align, int left, int top, int width, int height, bool scrolling, bool floating) { commonTagHandler(); if (name.empty()) { results->addToList (results->createError ("Got FRAME tag without frame name!")); commonAfterTagHandler(); return; } string nm = lcase (name); string act = lcase (action); string alg = lcase (align); string tt = title; //name is the default title if (tt.empty()) tt = name; //align alignType at = Top; if (!align.empty()) { bool alignok = false; if (align == "left") { at = Left; alignok = true; } if (align == "right") { at = Right; alignok = true; } if (align == "top") { at = Top; alignok = true; } if (align == "bottom") { at = Bottom; alignok = true; } if (!alignok) results->addToList (results->createError ("Received FRAME tag with unknown ALIGN option!")); } //does the list of frames contain frame with name nm? bool nmExists = (frames.count (nm) != 0); if (act == "open") { if (nmExists) { results->addToList (results->createError ("Received request to create an existing frame!")); commonAfterTagHandler(); return; } //cannot create _top or _previous if ((nm == "_top") || (nm == "_previous")) { results->addToList (results->createError ("Received request to create a frame with name " + nm + ", which is invalid!")); commonAfterTagHandler(); return; } if (internal) { //false for internal windows... value not used as of now, but it may be used later... frames[nm] = false; results->addToList (results->createInternalWindow (nm, tt, at, scrolling)); } else { //true for normal windows... value not used as of now, but it may be used later... frames[nm] = true; results->addToList (results->createWindow (nm, tt, left, top, width, height, scrolling, floating)); } } if (act == "close") { if (nmExists) { frames.erase (nm); results->addToList (results->createCloseWindow (nm)); } else results->addToList (results->createError ("Received request to close a non-existing frame!")); } if (act == "redirect") { //if the frame exists, or if the name is either _top or _previous, we redirect to that window if ((nm == "_top") || (nm == "_previous") || nmExists) redirectTo (nm); else { //create that window if (internal) { //false for internal windows... value not used as of now, but it may be used later... frames[nm] = false; results->addToList (results->createInternalWindow (nm, tt, at, scrolling)); } else { //true for normal windows... value not used as of now, but it may be used later... frames[nm] = true; results->addToList (results->createWindow (nm, tt, left, top, width, height, scrolling, floating)); } //then redirect to it redirectTo (nm); } } commonAfterTagHandler(); } void cMXPState::redirectTo (const string &name) { string nm = lcase (name); string emptystring; mxpResult *res = 0; if (nm == "_top") res = results->createSetWindow (emptystring); else if (nm == "_previous") res = results->createSetWindow (prevWindow); else if (frames.count (nm)) res = results->createSetWindow (nm); else res = results->createError ("Received request to redirect to non-existing window " + nm); //apply result - will update info about previous window and so... applyResult (res); results->addToList (res); } void cMXPState::gotDEST (const string &name, int x, int y, bool eol, bool eof) { commonTagHandler(); string nm = lcase (name); bool nmExists = (frames.count (nm) != 0); if (!nmExists) { results->addToList (results->createError ("Received a request to redirect to non-existing window " + nm)); return; } mxpResult *res = results->createSetWindow (name); mxpResult *res2 = createClosingResult (res); applyResult (res); results->addToList (res); int _x = x; int _y = y; if ((y >= 0) && (x < 0)) _x = 0; if ((_x >= 0) && (_y >= 0)) results->addToList (results->createMoveCursor (_x, _y)); list *ls = 0; //erase AFTER displaying text if (eol || eof) { ls = new list; ls->push_back (res2); res2 = results->createEraseText (eof); } //closing tag... addClosingTag ("dest", res2, ls); commonAfterTagHandler(); } //crosslinking servers void cMXPState::gotRELOCATE (const string &hostname, int port) { commonTagHandler(); results->addToList (results->createRelocate (hostname, port)); commonAfterTagHandler(); } void cMXPState::gotUSER () { commonTagHandler(); results->addToList (results->createSendLogin (true)); commonAfterTagHandler(); } void cMXPState::gotPASSWORD () { commonTagHandler(); results->addToList (results->createSendLogin (false)); commonAfterTagHandler(); } //images void cMXPState::gotIMAGE (const string &fname, const string &url, const string &type, int height, int width, int hspace, int vspace, const string &align, bool ismap) { commonTagHandler(); //align string alg = lcase (align); alignType at = Top; if (!align.empty()) { bool alignok = false; if (align == "left") { at = Left; alignok = true; } if (align == "right") { at = Right; alignok = true; } if (align == "top") { at = Top; alignok = true; } if (align == "bottom") { at = Bottom; alignok = true; } if (align == "middle") { at = Middle; alignok = true; } if (!alignok) results->addToList (results->createError ("Received IMAGE tag with unknown ALIGN option!")); } if (gotmap) results->addToList (results->createError ("Received multiple image maps in one SEND tag!")); if (ismap) { if (inLink && (!isALink)) { results->addToList (results->createImageMap (lastcmd)); lastcmd = ""; gotmap = true; } else results->addToList (results->createError ("Received an image map with no SEND tag!")); } results->addToList (results->createImage (fname, url, type, height, width, hspace, vspace, at)); commonAfterTagHandler(); } //closing tags void cMXPState::gotClosingTag (const string &name) { string nm = lcase (name); //hack, to prevent an error from being reported when or end-of-flag comes //we cannot simply test for and friends and disable it then, because //we could have the var tag inside some element bool oldInVar = inVar; inVar = false; commonTagHandler(); //restore the inVar variable... inVar = oldInVar; bool okay = false; while (!okay) { if (closingTags.empty()) break; //last one closed... //closingTags is a FIFO queue, tho technically it's a list closingTag *tag = closingTags.back (); closingTags.pop_back (); if (tag->name == nm) okay = true; //good else results->addToList (results->createWarning ("Had to auto-close tag " + tag->name + ", because closing tag was received.")); closeTag (tag); } if (!okay) results->addToList (results->createError ("Received unpaired closing tag .")); commonAfterTagHandler(); } void cMXPState::closeTag (closingTag *tag) { //some tags need special handling... if (tag->name == "p") { inParagraph = false; ignoreNextNewLine = false; //also send a newline after end of paragraph... MXP docs say nothing about this :( results->addToList (results->createText ("\r\n")); } if (tag->name == "var") { tag->closingresult = 0; tag->closingresults = 0; results->addToList (results->createVariable (varName, varValue)); results->addToList (results->createText (varName + ": " + varValue)); entities->addEntity (varName, varValue); inVar = false; varName = ""; varValue = ""; } if (tag->name == "a") { if (inLink && isALink) { // !!! SOME LOW-LEVEL MANIPULATIONS HERE !!! linkStruct *ls = (linkStruct *) tag->closingresult->data; //assign text, using URL if no text given string lt = linkText.empty() ? (ls->url ? ls->url : "") : linkText; ls->text = new char[lt.length() + 1]; ls->text[0] = '\0'; if (lt.length()) strcpy (ls->text, lt.c_str()); } else //this should never happen results->addToList (results->createError ("Received tag, but I'm not in a link!")); linkText = ""; inLink = false; isALink = false; } if (tag->name == "send") { if (gotmap) { //don't send this closing result results->deleteResult (tag->closingresult); tag->closingresult = 0; if (!linkText.empty()) results->addToList (results->createError ("Received image map and a command in one SEND tag!")); } else if (inLink && (!isALink)) { // !!! SOME LOW-LEVEL MANIPULATIONS HERE !!! sendStruct *ss = (sendStruct *) tag->closingresult->data; //assign text, also assign to command if none given //assign linkText to ss->text delete[] ss->text; ss->text = new char[linkText.length() + 1]; strcpy (ss->text, linkText.c_str()); if (ss->hint) { //expand &text; in hint string hint = ss->hint; bool found = true, havematch = false; while (found) { int p = hint.find ("&text;"); if (p < hint.length()) //found it { //replace it... hint.replace (p, 6, linkText); havematch = true; } else found = false; //no more matches } if (havematch) //apply changes if needed { //assign hint to ss->hint delete[] ss->hint; ss->hint = new char[hint.length() + 1]; strcpy (ss->hint, hint.c_str()); } } if (ss->command) { string cmd = ss->command; //also expand &text; in href bool found = true, havematch = false; while (found) { int p = cmd.find ("&text;"); if (p < cmd.length()) //found it { //replace it... cmd.replace (p, 6, linkText); havematch = true; } else found = false; //no more matches } if (havematch) //apply changes if needed { //assign cmd to ss->command delete[] ss->command; ss->command = new char[cmd.length() + 1]; strcpy (ss->command, cmd.c_str()); } } else if (!linkText.empty()) { //assign linkText to ss->command ss->command = new char[linkText.length() + 1]; strcpy (ss->command, linkText.c_str()); } } else //this should never happen results->addToList (results->createError ("Received tag, but I'm not in a link!")); linkText = ""; inLink = false; isALink = false; gotmap = false; } //handle applying/sending of closing results, is any if (tag->closingresult) { //apply result, reverting changes made by opening tag applyResult (tag->closingresult); //and send the changes to the client app results->addToList (tag->closingresult); } if (tag->closingresults) { //the same for remaining closing tags... list::iterator it; for (it = tag->closingresults->begin(); it != tag->closingresults->end(); ++it) { applyResult (*it); results->addToList (*it); } } //finally, the closing tag gets deleted //note that this won't delete the results themselves - they will be deleted after //they are processed by the client app delete tag->closingresults; tag->closingresults = 0; delete tag; } //mxpResult handling mxpResult *cMXPState::createClosingResult (mxpResult *what) { mxpResult *res = 0; switch (what->type) { case 3: { flagStruct *fs = (flagStruct *) what->data; res = results->createFlag (false, fs->name); break; } case 5: { formatStruct *fs = (formatStruct *) what->data; //usemask is the most relevant thing here - things not enabled there won't be applied, //so we can place anything there int usemask = fs->usemask; char curattrib = (bold?1:0) * Bold + (italic?1:0) * Italic + (underline?1:0) * Underline + (strikeout?1:0) * Strikeout; string font; if (usemask & USE_FONT) font = curfont; res = results->createFormatting (usemask, curattrib, fgcolor, bgcolor, font, cursize); break; } case 15: { res = results->createSetWindow (curWindow); break; } }; return res; } void cMXPState::applyResult (mxpResult *what) { switch (what->type) { case 5: { formatStruct *fs = (formatStruct *) what->data; int usemask = fs->usemask; if (usemask & USE_BOLD) bold = fs->attributes & Bold; if (usemask & USE_ITALICS) italic = fs->attributes & Italic; if (usemask & USE_UNDERLINE) underline = fs->attributes & Underline; if (usemask & USE_STRIKEOUT) strikeout = fs->attributes & Strikeout; if (usemask & USE_FG) fgcolor = fs->fg; if (usemask & USE_BG) bgcolor = fs->bg; if (usemask & USE_FONT) curfont = fs->font; if (usemask & USE_SIZE) cursize = fs->size; break; } case 15: { prevWindow = curWindow; if (what->data) curWindow = (char *) what->data; else curWindow = ""; break; }; }; } void cMXPState::addClosingTag (const string &name, mxpResult *res, list *res2) { closingTag *ctag = new closingTag; ctag->name = name; ctag->closingresult = res; ctag->closingresults = res2; closingTags.push_back (ctag); } void cMXPState::setScreenProps (int sx, int sy, int wx, int wy, int fx, int fy) { sX = sx; sY = sy; wX = wx; wY = wy; fX = fx; fY = fy; } int cMXPState::computeCoord (const string &coord, bool isX, bool inWindow) { int retval = atoi (coord.c_str()); int len = coord.length(); char ch = coord[len - 1]; if (ch == 'c') retval *= (isX ? fX : fY); if (ch == '%') retval = retval * (inWindow ? (isX ? wX : wY) : (isX ? sX : sY)) / 100; return retval; }