/*---------------------------------------------------------------------------- | Copyright (C) 1999 Jochen C. Loewer (loewerj@hotmail.com) +----------------------------------------------------------------------------- | | Rcsid: @(#)$Id$ | | The contents of this file are subject to the Mozilla Public License | Version 2.0 (the "License"); you may not use this file except in | compliance with the License. You may obtain a copy of the License at | http://www.mozilla.org/MPL/ | | Software distributed under the License is distributed on an "AS IS" | basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the | License for the specific language governing rights and limitations | under the License. | | The Original Code is tDOM. | | The Initial Developer of the Original Code is Jochen Loewer. | | Portions created by Jochen Loewer are Copyright (C) 1998, 1999 | Jochen Loewer. All Rights Reserved. | | Portions created by Zoran Vasiljevic are Copyright (C) 2000-2002 | Zoran Vasiljevic. All Rights Reserved. | | Portions created by Rolf Ade are Copyright (C) 1999-2002 | Rolf Ade. All Rights Reserved. | | Written by Zoran Vasiljevic | July 12, 2000 | \---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------- | Includes | \---------------------------------------------------------------------------*/ #include #include #include #include #define PARSER_NODE 9999 /* Hack so that we can invoke XML parser */ /* More hacked domNodeTypes - used to signal, that we want to check name/data of the node to create. */ #define ELEMENT_NODE_ANAME_CHK 10000 #define ELEMENT_NODE_AVALUE_CHK 10001 #define ELEMENT_NODE_CHK 10002 #define TEXT_NODE_CHK 10003 #define COMMENT_NODE_CHK 10004 #define CDATA_SECTION_NODE_CHK 10005 #define PROCESSING_INSTRUCTION_NODE_NAME_CHK 10006 #define PROCESSING_INSTRUCTION_NODE_VALUE_CHK 10007 #define PROCESSING_INSTRUCTION_NODE_CHK 10008 /*---------------------------------------------------------------------------- | Types | | This structure represents one stack slot. The stack itself | is implemented as double-linked-list of following structures. | \---------------------------------------------------------------------------*/ typedef struct StackSlot { void *element; /* The stacked element */ struct StackSlot *nextPtr; /* Next link */ struct StackSlot *prevPtr; /* Previous link */ } StackSlot; /*---------------------------------------------------------------------------- | Beginning of the stack and current element pointer are local | to current thread and also local to this file. | For non-threaded environments, it's a regular static. | \---------------------------------------------------------------------------*/ typedef struct CurrentStack { StackSlot *elementStack; StackSlot *currentSlot; } CurrentStack; /*---------------------------------------------------------------------------- | Structure used as clientData of the created commands. | The structure stores, which type of node the command has | to create and, in case of elementNodes and if given, the | namespace of the node. \---------------------------------------------------------------------------*/ typedef struct NodeInfo { int type; char *namespace; int jsonType; char *tagName; } NodeInfo; #ifndef TCL_THREADS static CurrentStack dataKey; # define TSDPTR(a) a #else static Tcl_ThreadDataKey dataKey; # define TSDPTR(a) (CurrentStack*)Tcl_GetThreadData((a),sizeof(CurrentStack)) #endif /*---------------------------------------------------------------------------- | Forward declarations | \---------------------------------------------------------------------------*/ static void * StackPush (void *); static void * StackPop (void); static void * StackTop (void); static int NodeObjCmd (ClientData,Tcl_Interp*,int,Tcl_Obj *const o[]); static void StackFinalize (ClientData); extern int tcldom_appendXML (Tcl_Interp*, domNode*, Tcl_Obj*); /*---------------------------------------------------------------------------- | StackPush | \---------------------------------------------------------------------------*/ static void * StackPush ( void *element ) { StackSlot *newElement; CurrentStack *tsdPtr = TSDPTR(&dataKey); /*------------------------------------------------------------------- | Reuse already allocated stack slots, if any | \------------------------------------------------------------------*/ if (tsdPtr->currentSlot && tsdPtr->currentSlot->nextPtr) { tsdPtr->currentSlot = tsdPtr->currentSlot->nextPtr; tsdPtr->currentSlot->element = element; return element; } /*------------------------------------------------------------------- | Allocate new stack slot | \------------------------------------------------------------------*/ newElement = (StackSlot *)MALLOC(sizeof(StackSlot)); memset(newElement, 0, sizeof(StackSlot)); if (tsdPtr->elementStack == NULL) { tsdPtr->elementStack = newElement; #ifdef TCL_THREADS Tcl_CreateThreadExitHandler(StackFinalize, tsdPtr->elementStack); #else Tcl_CreateExitHandler (StackFinalize, tsdPtr->elementStack); #endif } else { tsdPtr->currentSlot->nextPtr = newElement; newElement->prevPtr = tsdPtr->currentSlot; } tsdPtr->currentSlot = newElement; tsdPtr->currentSlot->element = element; return element; } /*---------------------------------------------------------------------------- | StackPop - pops the element from stack | \---------------------------------------------------------------------------*/ static void * StackPop (void) { void *element; CurrentStack *tsdPtr = TSDPTR(&dataKey); element = tsdPtr->currentSlot->element; if (tsdPtr->currentSlot->prevPtr) { tsdPtr->currentSlot = tsdPtr->currentSlot->prevPtr; } else { tsdPtr->currentSlot->element = NULL; } return element; } /*---------------------------------------------------------------------------- | StackTop - returns top-level element from stack | \---------------------------------------------------------------------------*/ static void * StackTop (void) { CurrentStack *tsdPtr = TSDPTR(&dataKey); if (tsdPtr->currentSlot == NULL) { return NULL; } return tsdPtr->currentSlot->element; } /*---------------------------------------------------------------------------- | StackFinalize - reclaims stack memory (slots only, not elements) | \---------------------------------------------------------------------------*/ static void StackFinalize ( ClientData clientData ) { StackSlot *tmp, *stack = (StackSlot *)clientData; while (stack) { tmp = stack->nextPtr; FREE((char*)stack); stack = tmp; } } /* *---------------------------------------------------------------------- * * namespaceTail -- * * Returns the trailing name at the end of a string with "::" * namespace qualifiers. These qualifiers are namespace names * separated by "::"s. For example, for "::foo::p" this function * returns a pointer to the "p" in that obj string rep, and for * "::" it returns a pointer to "". * * Results: * Returns a pointer to the start of the tail name. * * Side effects: * None. * *---------------------------------------------------------------------- */ static char* namespaceTail ( Tcl_Obj *nameObj ) { char *name,*p; int len; name = Tcl_GetStringFromObj(nameObj, &len); p = name + len; /* Isolate just the tail name, i.e. skip it's parent namespace */ while (--p > name) { if ((*p == ':') && (*(p-1) == ':')) { p++; /* just after the last "::" */ name = p; break; } } return name; } /*---------------------------------------------------------------------------- | NodeObjCmdDeleteProc | \---------------------------------------------------------------------------*/ static void NodeObjCmdDeleteProc ( ClientData clientData ) { NodeInfo *nodeInfo = (NodeInfo *) clientData; if (nodeInfo->namespace) { FREE (nodeInfo->namespace); } if (nodeInfo->tagName) { FREE (nodeInfo->tagName); } FREE (nodeInfo); } /*---------------------------------------------------------------------------- | NodeObjCmd | \---------------------------------------------------------------------------*/ static int NodeObjCmd ( ClientData arg, /* Type of node to create. */ Tcl_Interp * interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[] /* Argument objects. */ ) { int type, createType, len, dlen, i, ret, disableOutputEscaping = 0, index = 1; char *tag, *p, *tval, *aval; domNode *parent, *newNode = NULL; domTextNode *textNode = NULL; domDocument *doc; Tcl_Obj *cmdObj, **opts; NodeInfo *nodeInfo = (NodeInfo*) arg; /*------------------------------------------------------------------------ | Need parent node to get the owner document and to append new | child tag to it. The current parent node is stored on the stack. | \-----------------------------------------------------------------------*/ parent = (domNode *)StackTop(); if (parent == NULL) { Tcl_AppendResult(interp, "called outside domNode context", NULL); return TCL_ERROR; } doc = parent->ownerDocument; /*------------------------------------------------------------------------ | Create new node according to type. Special case is the ELEMENT_NODE | since here we may enter into recursion. The ELEMENT_NODE is the only | node type which may have script body as last argument. | \-----------------------------------------------------------------------*/ ret = TCL_OK; type = nodeInfo->type; switch (abs(type)) { case CDATA_SECTION_NODE: case CDATA_SECTION_NODE_CHK: case COMMENT_NODE: case COMMENT_NODE_CHK: case TEXT_NODE: case TEXT_NODE_CHK: if (objc != 2) { if (abs(type) == TEXT_NODE || abs(type) == TEXT_NODE_CHK) { if (objc != 3 || strcmp ("-disableOutputEscaping", Tcl_GetStringFromObj (objv[1], &len))!=0) { Tcl_WrongNumArgs(interp, 1, objv, "?-disableOutputEscaping? text"); return TCL_ERROR; } else { disableOutputEscaping = 1; index = 2; } } else { Tcl_WrongNumArgs(interp, 1, objv, "text"); return TCL_ERROR; } } tval = Tcl_GetStringFromObj(objv[index], &len); switch (abs(type)) { case TEXT_NODE_CHK: if (!tcldom_textCheck (interp, tval, "text")) return TCL_ERROR; createType = TEXT_NODE; break; case COMMENT_NODE_CHK: if (!tcldom_commentCheck (interp, tval)) return TCL_ERROR; createType = COMMENT_NODE; break; case CDATA_SECTION_NODE_CHK: if (!tcldom_CDATACheck (interp, tval)) return TCL_ERROR; createType = CDATA_SECTION_NODE; break; default: createType = nodeInfo->type; break; } textNode = domNewTextNode(doc, tval, len, createType); textNode->info = nodeInfo->jsonType; if (disableOutputEscaping) { textNode->nodeFlags |= DISABLE_OUTPUT_ESCAPING; } domAppendChild(parent, (domNode*) textNode); break; case PROCESSING_INSTRUCTION_NODE_NAME_CHK: case PROCESSING_INSTRUCTION_NODE_VALUE_CHK: case PROCESSING_INSTRUCTION_NODE_CHK: case PROCESSING_INSTRUCTION_NODE: if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "target data"); return TCL_ERROR; } tval = Tcl_GetStringFromObj(objv[1], &len); if (abs(type) == PROCESSING_INSTRUCTION_NODE_NAME_CHK || abs(type) == PROCESSING_INSTRUCTION_NODE_CHK) { if (!tcldom_PINameCheck (interp, tval)) return TCL_ERROR; } aval = Tcl_GetStringFromObj(objv[2], &dlen); if (abs(type) == PROCESSING_INSTRUCTION_NODE_VALUE_CHK || abs(type) == PROCESSING_INSTRUCTION_NODE_CHK) { if (!tcldom_PIValueCheck (interp, aval)) return TCL_ERROR; } newNode = (domNode *) domNewProcessingInstructionNode(doc, tval, len, aval, dlen); domAppendChild(parent, newNode); break; case PARSER_NODE: /* non-standard node-type : a hack! */ if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "markup"); return TCL_ERROR; } ret = tcldom_appendXML(interp, parent, objv[1]); break; case ELEMENT_NODE_ANAME_CHK: case ELEMENT_NODE_AVALUE_CHK: case ELEMENT_NODE_CHK: case ELEMENT_NODE: if (!nodeInfo->tagName) { tag = Tcl_GetStringFromObj(objv[0], &len); p = tag + len; /* Isolate just the tag name, i.e. skip it's parent namespace */ while (--p > tag) { if ((*p == ':') && (*(p-1) == ':')) { p++; /* just after the last "::" */ tag = p; break; } } } else { tag = nodeInfo->tagName; } newNode = domAppendNewElementNode (parent, tag, nodeInfo->namespace); newNode->info = nodeInfo->jsonType; /* * Allow for following syntax: * cmd ?-option value ...? ?script? * cmd ?opton value ...? ?script? * cmd key_value_list script * where list contains "-key value ..." or "key value ..." */ if ((objc % 2) == 0) { cmdObj = objv[objc-1]; len = objc - 2; /* skip both command and script */ opts = (Tcl_Obj**)objv + 1; } else if((objc == 3) && Tcl_ListObjGetElements(interp,objv[1],&len,&opts)==TCL_OK && (len == 0 || len > 1)) { if ((len % 2)) { Tcl_AppendResult(interp, "list must have " "an even number of elements", NULL); return TCL_ERROR; } cmdObj = objv[2]; } else { cmdObj = NULL; len = objc - 1; /* skip command */ opts = (Tcl_Obj**)objv + 1; } for (i = 0; i < len; i += 2) { tval = Tcl_GetString(opts[i]); if (*tval == '-') { tval++; } if (abs(type) == ELEMENT_NODE_ANAME_CHK || abs(type) == ELEMENT_NODE_CHK) { if (!tcldom_nameCheck (interp, tval, "attribute", 0)) { return TCL_ERROR; } } aval = Tcl_GetString(opts[i+1]); if (abs(type) == ELEMENT_NODE_AVALUE_CHK || abs(type) == ELEMENT_NODE_CHK) { if (!tcldom_textCheck (interp, aval, "attribute")) { return TCL_ERROR; } } domSetAttribute(newNode, tval, aval); } if (cmdObj) { ret = nodecmd_appendFromScript(interp, newNode, cmdObj); } break; } if (type < 0 && newNode != NULL) { char buf[64]; tcldom_createNodeObj(interp, newNode, buf); Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, strlen(buf))); } if (ret == TCL_OK) doc->nodeFlags |= NEEDS_RENUMBERING; return ret; } /*---------------------------------------------------------------------------- | nodecmd_createNodeCmd - implements the "createNodeCmd" method of | "dom" Tcl command | | This command is used to generate other Tcl commands which in turn | generate tDOM nodes. These new commands can only be called within | the context of the domNode command, however. | | Syntax: dom createNodeCmd ?-returnNodeCmd? cmdName | | where can be one of: | elementNode, commentNode, textNode, cdataNode or piNode | | The optional "-returnNodeCmd" parameter, if given, instructs the | command to return the object-based node command of the newly generated | node. Without the parameter, the command returns current interp result. | | Example: | | % dom createNodeCmd elementNode html::body | % dom createNodeCmd -returnNodeCmd elementNode html::title | % dom createNodeCmd textNode html::t | | And usage: | | % set d [dom createDocument html] | % set n [$d documentElement] | % $n appendFromScript { | html::body { | set title [html::title {html::t "This is an example"}] | $title setAttribute dummy 1 | } | % puts [$n asHTML] | \---------------------------------------------------------------------------*/ int nodecmd_createNodeCmd ( Tcl_Interp * interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[], /* Argument objects. */ int checkName, /* Flag: Name checks? */ int checkCharData /* Flag: Data checks? */ ) { int index, ret, type, nodecmd = 0, jsonType = 0, haveJsonType = 0; int isElement = 0; char *nsName, buf[64]; Tcl_Obj *tagName = NULL, *namespace = NULL; Tcl_DString cmdName; NodeInfo *nodeInfo; /* * Syntax: * * dom createNodeCmd ?-returnNodeCmd? nodeType commandName */ enum subCmd { ELM_NODE, TXT_NODE, CDS_NODE, CMT_NODE, PIC_NODE, PRS_NODE }; static const char *subcmds[] = { "elementNode", "textNode", "cdataNode", "commentNode", "piNode", "parserNode", NULL }; static const char *options[] = { "-returnNodeCmd", "-jsonType", "-tagName", "-namespace", NULL }; enum option { o_returnNodeCmd, o_jsonType, o_tagName, o_namespace }; static const char *jsonTypes[] = { "NONE", "ARRAY", "OBJECT", "NULL", "TRUE", "FALSE", "STRING", "NUMBER" }; if (objc < 3 ) { goto usage; } while (objc > 3) { if (Tcl_GetIndexFromObj (interp, objv[1], options, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch ((enum option) index) { case o_returnNodeCmd: nodecmd = 1; objc--; objv++; break; case o_jsonType: if (Tcl_GetIndexFromObj (interp, objv[2], jsonTypes, "jsonType", 1, &jsonType) != TCL_OK) { return TCL_ERROR; } haveJsonType = 1; objc -= 2; objv += 2; break; case o_tagName: tagName = objv[2]; objc -= 2; objv += 2; break; case o_namespace: namespace = objv[2]; objc -= 2; objv += 2; break; } } if (objc != 3) { goto usage; } ret = Tcl_GetIndexFromObj(interp, objv[1], subcmds, "nodeType", 0, &index); if (ret != TCL_OK) { return ret; } /*-------------------------------------------------------------------- | Construct fully qualified command name using current namespace | \-------------------------------------------------------------------*/ Tcl_DStringInit(&cmdName); strcpy(buf, "namespace current"); ret = Tcl_Eval(interp, buf); if (ret != TCL_OK) { return ret; } nsName = (char *)Tcl_GetStringResult(interp); Tcl_DStringAppend(&cmdName, nsName, -1); if (strcmp(nsName, "::")) { Tcl_DStringAppend(&cmdName, "::", 2); } Tcl_DStringAppend(&cmdName, Tcl_GetString(objv[2]), -1); Tcl_ResetResult (interp); switch ((enum subCmd)index) { case ELM_NODE: isElement = 1; if (!haveJsonType) { if (!tcldom_nameCheck(interp, namespaceTail(objv[2]), "tag", 0)) { return TCL_ERROR; } if (checkName && checkCharData) { type = ELEMENT_NODE_CHK; } else if (checkName) { type = ELEMENT_NODE_ANAME_CHK; } else if (checkCharData) { type = ELEMENT_NODE_AVALUE_CHK; } else { type = ELEMENT_NODE; } } else { if (jsonType > 2) { Tcl_SetResult(interp, "For an element node the jsonType" " argument must be one out of this list: ARRAY" " OBJECT NONE.", NULL); return TCL_ERROR; } type = ELEMENT_NODE; } break; case PRS_NODE: type = PARSER_NODE; break; case TXT_NODE: if (!haveJsonType) { if (checkCharData) { type = TEXT_NODE_CHK; } else { type = TEXT_NODE; } } else { if (jsonType < 3 && jsonType > 0) { Tcl_SetResult(interp, "For a text node the jsonType " "argument must be one out of this list: " "TRUE FALSE NULL NUMBER STRING NONE", NULL); return TCL_ERROR; } type = TEXT_NODE; } break; case CDS_NODE: if (checkCharData) { type = CDATA_SECTION_NODE_CHK; } else { type = CDATA_SECTION_NODE; } break; case CMT_NODE: if (checkCharData) { type = COMMENT_NODE_CHK; } else { type = COMMENT_NODE; } break; case PIC_NODE: if (checkName && checkCharData) { type = PROCESSING_INSTRUCTION_NODE_CHK; } else if (checkName) { type = PROCESSING_INSTRUCTION_NODE_NAME_CHK; } else if (checkCharData) { type = PROCESSING_INSTRUCTION_NODE_VALUE_CHK; } else { type = PROCESSING_INSTRUCTION_NODE; } break; default: Tcl_SetResult (interp, "Invalid/unexpected node type", NULL); return TCL_ERROR; } if (tagName && !isElement) { Tcl_SetResult(interp, "The -tagName option is allowed only for " "element node commands.", NULL); return TCL_ERROR; } if (namespace && !isElement) { Tcl_SetResult(interp, "The -namespace option is allowed only for " "element node commands.", NULL); return TCL_ERROR; } if (haveJsonType && type != ELEMENT_NODE && type != TEXT_NODE) { Tcl_SetResult(interp, "Only element and text nodes may have a " "JSON type.", NULL); return TCL_ERROR; } nodeInfo = (NodeInfo *) MALLOC (sizeof (NodeInfo)); nodeInfo->namespace = NULL; nodeInfo->type = type; if (nodecmd) { nodeInfo->type *= -1; /* Signal this fact */ } nodeInfo->jsonType = jsonType; nodeInfo->tagName = NULL; if (namespace) { nodeInfo->namespace = tdomstrdup (Tcl_GetString(namespace)); } if (tagName) { nodeInfo->tagName = tdomstrdup (Tcl_GetString(tagName)); } Tcl_CreateObjCommand(interp, Tcl_DStringValue(&cmdName), NodeObjCmd, (ClientData)nodeInfo, NodeObjCmdDeleteProc); Tcl_DStringResult(interp, &cmdName); Tcl_DStringFree(&cmdName); return TCL_OK; usage: Tcl_AppendResult(interp, "dom createNodeCmd\n" "\t?-returnNodeCmd?\n" "\t?-jsonType ?\n" "\t?-tagName ?\n" " nodeType cmdName", NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * nodecmd_appendFromScript -- * * This procedure implements the dom method appendFromScript. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * Appends new child nodes to node. * *---------------------------------------------------------------------- */ int nodecmd_appendFromScript ( Tcl_Interp *interp, /* Current interpreter. */ domNode *node, /* Parent dom node */ Tcl_Obj *cmdObj /* Argument objects. */ ) { int ret; domNode *oldLastChild, *child, *nextChild; if (node->nodeType != ELEMENT_NODE) { Tcl_SetResult (interp, "NOT_AN_ELEMENT : can't append nodes", NULL); return TCL_ERROR; } oldLastChild = node->lastChild; StackPush((void *)node); Tcl_AllowExceptions(interp); ret = Tcl_EvalObjEx(interp, cmdObj, 0); if (ret != TCL_ERROR) { Tcl_ResetResult(interp); } StackPop(); if (ret == TCL_ERROR) { if (oldLastChild) { child = oldLastChild->nextSibling; } else { child = node->firstChild; } while (child) { nextChild = child->nextSibling; domFreeNode (child, NULL, NULL, 0); child = nextChild; } if (oldLastChild) { oldLastChild->nextSibling = NULL; node->lastChild = oldLastChild; } else { node->firstChild = NULL; node->lastChild = NULL; } } return (ret == TCL_BREAK) ? TCL_OK : ret; } /* *---------------------------------------------------------------------- * * nodecmd_insertBeforeFromScript -- * * This procedure implements the dom method * insertBeforeFromScript. See the user documentation for details * on what it does. * * This procedure is actually mostly a wrapper around * nodecmd_appendFromScript. * * Results: * A standard Tcl result. * * Side effects: * Insert new child nodes before referenceChild to node. * *---------------------------------------------------------------------- */ int nodecmd_insertBeforeFromScript ( Tcl_Interp *interp, /* Current interpreter. */ domNode *node, /* Parent dom node */ Tcl_Obj *cmdObj, /* Argument objects. */ domNode *refChild /* Insert new childs before this * node; may be NULL */ ) { int ret; domNode *storedLastChild, *n; if (!refChild) { return nodecmd_appendFromScript (interp, node, cmdObj); } if (node->nodeType != ELEMENT_NODE) { Tcl_SetResult (interp, "NOT_AN_ELEMENT : can't append nodes", NULL); return TCL_ERROR; } /* check, if node is in deed the parent of refChild */ if (refChild->parentNode != node) { /* If node is the root node of a document and refChild is in deed a child of this node, then refChild->parentNode will be NULL. In this case, we loop throu the childs of node, to see, if the refChild is valid. */ Tcl_ResetResult (interp); if (node->ownerDocument->rootNode == node) { n = node->firstChild; while (n) { if (n == refChild) { /* refChild is in deed a child of node */ break; } n = n->nextSibling; } if (!n) { Tcl_SetStringObj(Tcl_GetObjResult(interp), "NOT_FOUND_ERR", -1); return TCL_ERROR; } } else { Tcl_SetStringObj(Tcl_GetObjResult(interp), "NOT_FOUND_ERR", -1); return TCL_ERROR; } } storedLastChild = node->lastChild; if (refChild->previousSibling) { refChild->previousSibling->nextSibling = NULL; node->lastChild = refChild->previousSibling; } else { node->firstChild = NULL; node->lastChild = NULL; } ret = nodecmd_appendFromScript (interp, node, cmdObj); if (node->lastChild) { node->lastChild->nextSibling = refChild; refChild->previousSibling = node->lastChild; } else { node->firstChild = refChild; } node->lastChild = storedLastChild; return ret; } /*---------------------------------------------------------------------------- | nodecmd_curentNode | \---------------------------------------------------------------------------*/ void * nodecmd_currentNode(void) { return StackTop(); } /* EOF $RCSfile $ */ /* Emacs Setup Variables */ /* Local Variables: */ /* mode: C */ /* indent-tabs-mode: nil */ /* c-basic-offset: 4 */ /* End: */