
MMG(attribute="secure")
MMG(info="Module: Printing XML")
MMG(windows: coption="-I../../../")

#ifndef DARWIN
#include <malloc.h>
#endif
#include <map>
#include <mut/ustring_conv.h>

// TODO: For Darwin, convert using NSString:initWithCString:
// but this must be done in a file with suffix .m or .mm.
#ifdef linux
#include <iconv.h>
// workaround: some version of glibc have a second argument
// of const char**, while others use a char**.  The template
// is a worse match than a direct call, so no ambiguities
// result.
template <typename T>
inline
size_t iconv(iconv_t cd,
         T ** inbuf,
         size_t * inbytesleft,
         char ** outbuf,
         size_t * outbytesleft) {
  return iconv(cd, const_cast<const T**>(inbuf), inbytesleft,
           outbuf, outbytesleft);
}
#endif

namespace {

    const char *const kErrNumArgs = "Illegal number of arguments";
    const char *const kErrArg = "Illegal argument";
    const char *const kErrNoFilePtr = "Not a valid file descriptor";
    const char *const kErrOpen = "Cannot open file";
    const char *const kErrWrite = "Cannot write to file";
    const char *const kErrNoMem = "Out of memory";
    const char *const kErrName = "Illegal element or attribute name";
    const char *const kErrData = "Illegal character data";
    const char *const kErrAttr = "Illegal attribute";
    const char *const kErrOpt = "Illegal options";

#ifdef WIN32
    const char* const kOpenMode = "wb";
#else
    const char* const kOpenMode = "w";
#endif

    inline void FPuts(const char* s, FILE* str) {
        if (fputs(s, str) == EOF)
            MFerror(kErrWrite);
    }

    inline void FPutc(int c, FILE* str) {
        if (putc(c, str) == EOF)
            MFerror(kErrWrite);
    }

    // Convert 'data' string to utf8 and output as PCDATA on 'str'.
  void putPCData(const char* data, FILE* str)
  {
    // convert to utf8
    size_t dlen = ::strlen(data);
    if (!dlen) return;
    size_t ulen = 0;
    char* buf = 0;
#ifdef WIN32
    // UTF8 support only for Windows at the moment
    try {
      ulen = Mut::locToUtf8Length(data, dlen);
      buf = (char*)::alloca(ulen);
      if (!buf)
    MFerror(kErrNoMem);
      ulen = Mut::locToUtf8(data, dlen, buf, ulen);
    } catch (...) {
      MFerror(kErrNoMem);
    }
#else
#ifdef linux
    iconv_t converter = iconv_open("UTF8", "8859_1");
    if((iconv_t)-1 == converter) {
      if(EINVAL == errno) {
    fprintf(stderr, "iconv_open does not like the conversion\n");
      }
      ulen = dlen;
      buf = (char *) data;
    } else {
      bool done = false;
      ulen = dlen;
      buf = NULL;
      while(!done) {
    free(buf);
    buf = (char*) MFcmalloc(ulen);
    char *c = buf;
    char *d = (char *)data;
    size_t rem = ulen;
    size_t inp = dlen;
    int ret = iconv(converter, &d, &inp, &c, &rem);
    if(0 <= ret) {
      done = true;
      ulen -= rem;
    } else if(E2BIG == errno) {
      ulen = (int)(1.3*ulen)+1;
    } else {
      MFerror(kErrData);
    }
      }
      iconv_close(converter);
    }
#else
    ulen = dlen;
    buf = (char *) data;
#endif
#endif
    // output data
    char *c = buf;
    for (; ulen > 0; ++c, --ulen) {
      switch (*c) {
      case '&':  FPuts("&amp;",  str); break;
      case '<':  FPuts("&lt;",   str); break;
      case '>':  FPuts("&gt;",   str); break;
      case '"':  FPuts("&quot;", str); break;
      case '\'': FPuts("&apos;", str); break;
      default:   FPutc(*c, str);
      }
    }
#ifdef linux
    MFcfree(buf);
#endif
  }

    int colorComp(MTcell color, long idx)
    {
        MTcell comp = MFop(color, idx);
        if (MFdom(comp) != DOM_FLOAT)
            MFerror(kErrData);
        int v = (int)(MFdouble(comp) * 255.0 + 0.5);
        if (v <= 0) return 0;
        if (v > 255) return 255;
        return v;
    }

    // Convert date given by 'obj' to utf8 and put it onto 'str'.
    void putCellData(MTcell obj, FILE* str)
    {
        switch (MFdom(obj)) {
        case DOM_STRING:
            putPCData(MFstring(obj), str);
            return;
        case DOM_IDENT:
            putPCData(MFident(obj), str);
            return;
        case DOM_INT:
            if (fprintf(str, "%ld", MFlong(obj)) < 0)
                MFerror(kErrWrite);
            return;
        case DOM_FLOAT:
            if (fprintf(str, "%.12g", MFdouble(obj)) < 0)
                MFerror(kErrWrite);
            return;
        case DOM_BOOL:
            {
                MTbool b = MFbool3(obj);
                if (b == MCunknown)
                    MFerror(kErrData);
                FPutc(((b == MCtrue) ? '1' : '0'), str);
            }
            return;
        case DOM_LIST:
            {
                // try to interpret list as RGB(A) color value
                long n = MFnops(obj);
                if (n == 3) {
                    if (fprintf(str, "#%02X%02X%02X",
                                colorComp(obj, 0),
                                colorComp(obj, 1),
                                colorComp(obj, 2)) < 0)
                    {
                        MFerror(kErrWrite);
                    }
                } else if (n == 4) {
                    if (fprintf(str, "#%02X%02X%02X%02X",
                                colorComp(obj, 0),
                                colorComp(obj, 1),
                                colorComp(obj, 2),
                                colorComp(obj, 3)) < 0)
                    {
                        MFerror(kErrWrite);
                    }
                } else
                    break;
            }
            return;
        case DOM_EXPR:
        {
            if (MFisExpr(obj, _EXPRSEQ)) {
                long i, n = MFnops(obj);
                if (n > 1) {
                    putCellData(MFop(obj, 1), str);
                    for (i = 2; i < n; ++i) {
                        FPutc(' ', str);
                        putCellData(MFop(obj, i), str);
                    }
                }
            }
        }
        return;
        }
        MFerror(kErrData);
    }

    // Convert string or ident 'name' to string and put it onto 'str' raw.
    void putName(MTcell name, FILE* str)
    {
        switch (MFdom(name)) {
        case DOM_STRING:
            FPuts(MFstring(name), str);
            return;
        case DOM_IDENT:
            FPuts(MFident(name), str);
            return;
        }
        MFerror(kErrName);
    }

    typedef std::map<long, FILE*> FileMap;
    typedef std::map<long, char*> NameMap;

    // maps of descriptors to file ptrs and names
    FileMap gFileMap;
    NameMap gNameMap;

    // next file descriptor
    long gNextFD = 1;

    FILE* getFileDescr(MTcell num)
    {
        long fd = MFlong(num);
#ifndef WIN32
        if (fd == 0)
            return stdout;
#endif
        FileMap::iterator i = gFileMap.find(fd);
        if (i == gFileMap.end())
            return 0;

        return i->second;
    }
}

MFUNC( beginElement, MCnop )
{
    // Test the arguments
    int i, nargs = MVnargs;
    if (nargs < 2)
        MFerror(kErrNumArgs);
    MFargCheck(1, DOM_INT);

    // Get the system file descriptor from the MuPAD file descriptor
    FILE* str = getFileDescr(MFarg(1));
    if (!str)
        MFerror(kErrNoFilePtr);

    // put element tag
    FPutc('<', str);
    putName(MFarg(2), str);

    // put attributes
    bool closeElem = true;
    bool emptyElem = false;
    for (i = 3; i <= nargs; ++i) {
        MTcell eq = MFarg(i);
        if (MFisExpr(eq, _EQUAL)) {
            FPutc(' ', str);
            // put attribute name
            putName(MFop(eq, 1), str);
            // put attribute value
            FPutc('=', str);
            FPutc('"', str);
            putCellData(MFop(eq, 2), str);
            FPutc('"', str);
        } else {
            if (MFisIdent(eq, "NoClose"))
                closeElem = false;
            else if (MFisIdent(eq, "Empty"))
                emptyElem = true;
            else
                MFerror(kErrAttr);
        }
    }
    if (closeElem) {
        if (emptyElem) {
            FPutc('/', str);
        }
        FPutc('>', str);
    } else if (emptyElem) {
        MFerror(kErrOpt);
    }

    MFreturn( MFcopy(MVnull) );
}
MFEND

MFUNC( endElement, MCnop )
{
   // Test the arguments
   MFnargsCheck(2);
   MFargCheck(1, DOM_INT);

    // Get the system file descriptor from the MuPAD file descriptor
    FILE* str = getFileDescr(MFarg(1));
    if (!str)
        MFerror(kErrNoFilePtr);

   FPutc('<', str);
   FPutc('/', str);
   putName(MFarg(2), str);
   FPutc('>', str);

   MFreturn( MFcopy(MVnull) );
}
MFEND

MFUNC( data, MCnop )
{
    // Test the arguments
    int i, j, n, nargs = MVnargs;
    MTcell argi;

    if (nargs < 2)
        MFerror(kErrNumArgs);
    MFargCheck(1, DOM_INT);
    for (i = 2; i <= nargs; ++i) {
     argi = MFarg(i);
     switch (MFdom(argi)) {
     case DOM_STRING:
     case DOM_IDENT:
     case DOM_INT:
     case DOM_FLOAT:
       break;

     case DOM_BOOL:
       // only TRUE or FALSE allowed
       if (MFisUnknown(argi))
         MFerror(kErrArg);
       break;

     case DOM_LIST:
       n = MFnops(argi);
       // only lists with 3 or 4 floats allowed (RGB(A)-values)
       if (n != 3 && n != 4)
          MFerror(kErrArg);
       for (j = 0; j < n; j++) {
         if (MFdom(MFop(argi, j)) != DOM_FLOAT)
           MFerror(kErrArg);
       }
       break;

     default:
       MFerror(kErrArg);
     }
   }

    // Get the system file descriptor from the MuPAD file descriptor
    FILE* str = getFileDescr(MFarg(1));
    if (!str)
        MFerror(kErrNoFilePtr);

    // put data strings
    for (i = 2; i <= nargs; ++i) {
        putCellData(MFarg(i), str);
    }
    MFreturn(MFcopy(MVnull));
}
MFEND

MFUNC( raw, MCnop )
{
    // Test the arguments
    int i, nargs = MVnargs;
    if (nargs < 2)
        MFerror(kErrNumArgs);
    MFargCheck(1, DOM_INT);
    for (i = 2; i <= nargs; ++i) {
        MFargCheck(i, DOM_STRING);
    }

    // Get the system file descriptor from the MuPAD file descriptor
    FILE *str = getFileDescr(MFarg(1)) ;
    if ( str == NULL )
        MFerror(kErrNoFilePtr);

    // put strings
    for (i = 2; i <= nargs; ++i) {
        FPuts(MFstring(MFarg(i)), str);
    }
    MFreturn(MFcopy(MVnull));
}
MFEND


MFUNC( prP2, MCnop )
{
    // Test the arguments
    int i, j, n, nargs = MVnargs;
    MTcell argi;

    if (nargs < 3)
        MFerror(kErrNumArgs);
    MFargCheck(1, DOM_INT);
    for (i = 2; i <= nargs; ++i) {
     argi = MFarg(i);
     switch (MFdom(argi)) {
     case DOM_INT:
     case DOM_FLOAT:
       break;

     case DOM_LIST:
       n = MFnops(argi);
       // only lists with 3 or 4 floats allowed (RGB(A)-values)
       if (n != 3 && n != 4)
          MFerror(kErrArg);
       for (j = 0; j < n; ++j) {
           if (MFdom(MFop(argi, j)) != DOM_FLOAT)
             MFerror(kErrArg);
       }
       break;

     default:
       MFerror(kErrArg);
     }
   }

    // Get the system file descriptor from the MuPAD file descriptor
    FILE* str = getFileDescr(MFarg(1));
    if (!str)
        MFerror(kErrNoFilePtr);

    // write point begin
    FPuts("<P2>", str);
    // put data strings
    putCellData(MFarg(2), str);
    for (i = 3; i <= nargs; ++i) {
        FPutc(' ', str);
        putCellData(MFarg(i), str);
    }
    // write point end
    FPuts("</P2>", str);
    MFreturn(MFcopy(MVnull));
}
MFEND

MFUNC( prP3, MCnop )
{
    // Test the arguments
    int i, j, n, nargs = MVnargs;
    MTcell argi;

    if (nargs < 4)
        MFerror(kErrNumArgs);
    MFargCheck(1, DOM_INT);
    for (i = 2; i <= nargs; ++i) {
     argi = MFarg(i);
     switch (MFdom(argi)) {
     case DOM_INT:
     case DOM_FLOAT:
      break;

     case DOM_LIST:
       n = MFnops(argi);
       // only lists with 3 or 4 floats allowed (RGB(A)-values)
       if (n != 3 && n != 4)
          MFerror(kErrArg);
       for (j = 0; j < n; j++) {
         if (MFdom(MFop(argi, j)) != DOM_FLOAT)
           MFerror(kErrArg);
       }
       break;

     default:
       MFerror(kErrArg);
     }
   }

    // Get the system file descriptor from the MuPAD file descriptor
    FILE* str = getFileDescr(MFarg(1));
    if (!str)
        MFerror(kErrNoFilePtr);

    // write point begin
    FPuts("<P3>", str);
    // put data strings
    putCellData(MFarg(2), str);
    for (i = 3; i <= nargs; ++i) {
        FPutc(' ', str);
        putCellData(MFarg(i), str);
    }
    // write point end
    FPuts("</P3>", str);
    MFreturn(MFcopy(MVnull));
}
MFEND

MFUNC( fopen, MCnop )
{
    int nargs = MVnargs;
    if (nargs > 1)
        MFerror(kErrNumArgs);

    char* fname = 0;
    if (nargs == 0) {
        // create temp file name
#ifdef WIN32
        fname = _tempnam(NULL, "mx");
#else
        char path[256];
        strcpy(path, "/tmp/mupad.XXXXXX");
        int fd = mkstemp(path);
        if (fd == -1)
            MFerror(kErrOpen);
        fname = strdup(path);
        close(fd);
#endif
    } else {
        MFargCheck(1, DOM_STRING);
        fname = strdup(MFcstring(MFarg(1), MCaddr));
    }
    if (!MFisSecureFileAccess(fname, "w"))
    {
       MFerror(kErrOpen);
    }
    if (! fname)
        MFerror(kErrOpen);
    FILE* fp = fopen(fname, kOpenMode);
    if (!fp) {
        free(fname);
        MFerror(kErrOpen);
    }
    long fd = gNextFD;
    gFileMap.insert(std::pair<long, FILE*>(fd, fp));
    gNameMap.insert(std::pair<long, char*>(fd, fname));
    gNextFD++;

    MFreturn(MFmlong(fd));
}
MFEND

MFUNC( fname, MCnop )
{
    // Test the arguments
    MFnargsCheck(1);
    MFargCheck(1, DOM_INT);

    long fd = MFlong(MFarg(1));
#ifndef WIN32
    if (fd == 0)
        MFerror(kErrNoFilePtr);
#endif

    NameMap::iterator i = gNameMap.find(fd);
    if (i == gNameMap.end())
        MFerror(kErrArg);

    MFreturn(MFmstring(i->second));
}
MFEND

MFUNC( fclose, MCnop )
{
    // Test the arguments
    MFnargsCheck(1);
    MFargCheck(1, DOM_INT);

    long fd = MFlong(MFarg(1));
#ifndef WIN32
    if (fd == 0)
        MFerror(kErrNoFilePtr);
#endif

    FileMap::iterator fi = gFileMap.find(fd);
    if (fi == gFileMap.end())
        MFerror(kErrArg);
    int res = fclose(fi->second);
    gFileMap.erase(fi);

    NameMap::iterator ni = gNameMap.find(fd);
    if (ni != gNameMap.end()) {
        free(ni->second);
        gNameMap.erase(ni);
    }

    if (res == EOF)
        MFerror(kErrWrite);

    MFreturn(MFcopy(MVnull));
}
MFEND
