/*---------------------------------------------------------------------------- | Copyright (C) 1999 Jochen C. Loewer (loewerj@hotmail.com) +----------------------------------------------------------------------------- | | Rcsid: @(#)$Id: nodecmd.c,v 1.11 2002/11/01 00:38:58 rolf Exp $ | | The contents of this file are subject to the Mozilla Public License | Version 1.1 (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 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 #define PARSER_NODE 9999 /* Hack so that we can invoke XML parser */ /*---------------------------------------------------------------------------- | 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; #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 _ANSI_ARGS_((void *)); static void * StackPop _ANSI_ARGS_((void)); static void * StackTop _ANSI_ARGS_((void)); static int NodeObjCmd _ANSI_ARGS_((ClientData,Tcl_Interp*,int,Tcl_Obj *CONST o[])); static void domAppendChild1(domNode*, domNode *); #ifdef TCL_THREADS static void StackFinalize _ANSI_ARGS_((ClientData)); #endif extern int tcldom_appendXML (Tcl_Interp*, domNode*, Tcl_Obj*); /*---------------------------------------------------------------------------- | StackPush | \---------------------------------------------------------------------------*/ static void * StackPush (element) 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; TDomThreaded(Tcl_CreateThreadExitHandler(StackFinalize, tsdPtr->elementStack);) } 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 *element; CurrentStack *tsdPtr = TSDPTR(&dataKey); element = tsdPtr->currentSlot->element; if (tsdPtr->currentSlot->prevPtr) { tsdPtr->currentSlot = tsdPtr->currentSlot->prevPtr; } return element; } /*---------------------------------------------------------------------------- | StackTop - returns top-level element from stack | \---------------------------------------------------------------------------*/ static void * StackTop () { CurrentStack *tsdPtr = TSDPTR(&dataKey); if (tsdPtr->currentSlot == NULL) { return NULL; } return tsdPtr->currentSlot->element; } #ifdef TCL_THREADS /*---------------------------------------------------------------------------- | StackFinalize - reclaims stack memory (slots only, not elements) | \---------------------------------------------------------------------------*/ static void StackFinalize (clientData) ClientData clientData; { StackSlot *tmp, *stack = (StackSlot *)clientData; while (stack) { tmp = stack->nextPtr; FREE((char*)stack); stack = tmp; } } #endif /*---------------------------------------------------------------------------- | NodeObjCmd | \---------------------------------------------------------------------------*/ static int NodeObjCmd (arg, interp, objc, objv) 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, len, dlen, i, ret, disableOutputEscaping = 0, index = 1; char *tag, *p, *tval, *aval; domNode *parent, *newNode = NULL; domDocument *doc; Tcl_Obj *cmdObj, **opts; /*------------------------------------------------------------------------ | 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 = (int)(arg); switch (abs(type)) { case CDATA_SECTION_NODE: /* FALL-THRU */ case COMMENT_NODE: /* FALL-THRU */ case TEXT_NODE: if (objc != 2) { if ((int)arg == TEXT_NODE) { 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); newNode = (domNode*)domNewTextNode(doc, tval, len, (int)arg); if (disableOutputEscaping) { newNode->nodeFlags |= DISABLE_OUTPUT_ESCAPING; } domAppendChild1(parent, newNode); break; case PROCESSING_INSTRUCTION_NODE: if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "target data"); return TCL_ERROR; } tval = Tcl_GetStringFromObj(objv[1], &len); aval = Tcl_GetStringFromObj(objv[2], &dlen); newNode = (domNode *) domNewProcessingInstructionNode(doc, tval, len, aval, dlen); domAppendChild1(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: 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; } } newNode = (domNode *)domNewElementNode(doc, tag, ELEMENT_NODE); domAppendChild1(parent, newNode); /* * 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_GetStringFromObj(opts[i], NULL); if (*tval == '-') { tval++; } aval = Tcl_GetStringFromObj(opts[i+1], NULL); 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 (dummy, interp, objc, objv) ClientData dummy; /* Not used. */ Tcl_Interp * interp; /* Current interpreter. */ int objc; /* Number of arguments. */ Tcl_Obj *CONST objv[]; /* Argument objects. */ { int ix, index, ret, type, nodecmd = 0; char *nsName, buf[64]; Tcl_DString cmdName; /* * Syntax: * * dom createNodeCmd ?-returnNodeCmd? elementType commandName */ enum { ELM_NODE, TXT_NODE, CDS_NODE, CMT_NODE, PIC_NODE, PRS_NODE }; static CONST84 char *subcmd[] = { "elementNode", "textNode", "cdataNode", "commentNode", "piNode", "parserNode", NULL }; if (objc != 3 && objc != 4) { goto usage; } if (objc == 4) { if (strcmp(Tcl_GetStringFromObj(objv[1], NULL), "-returnNodeCmd")) { goto usage; } nodecmd = 1; ix = 2; } else { nodecmd = 0; ix = 1; } ret = Tcl_GetIndexFromObj(interp, objv[ix], subcmd, "option", 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_GetStringFromObj(objv[ix+1], NULL), -1); switch (index) { case PRS_NODE: type = PARSER_NODE; break; case ELM_NODE: type = ELEMENT_NODE; break; case TXT_NODE: type = TEXT_NODE; break; case CDS_NODE: type = CDATA_SECTION_NODE; break; case CMT_NODE: type = COMMENT_NODE; break; case PIC_NODE: type = PROCESSING_INSTRUCTION_NODE; break; } if (nodecmd) { type *= -1; /* Signal this fact */ } Tcl_CreateObjCommand(interp, Tcl_DStringValue(&cmdName), NodeObjCmd, (ClientData)type, NULL); Tcl_DStringResult(interp, &cmdName); Tcl_DStringFree(&cmdName); return TCL_OK; usage: Tcl_AppendResult(interp, "dom createNodeCmd ?-returnNodeCmd? elementType cmdName", NULL); return TCL_ERROR; } /*---------------------------------------------------------------------------- | nodecmd_appendFromScript | \---------------------------------------------------------------------------*/ int nodecmd_appendFromScript (interp, node, cmdObj) Tcl_Interp *interp; /* Current interpreter. */ domNode *node; /* Parent dom node */ Tcl_Obj *cmdObj; /* Argument objects. */ { int ret; StackPush((void *)node); Tcl_AllowExceptions(interp); ret = Tcl_EvalObj(interp, cmdObj); if (ret != TCL_ERROR) { Tcl_ResetResult(interp); } StackPop(); return (ret == TCL_BREAK) ? TCL_OK : ret; } /*---------------------------------------------------------------------------- | nodecmd_curentNode | \---------------------------------------------------------------------------*/ void * nodecmd_currentNode(void) { return StackTop(); } /*--------------------------------------------------------------------------- | domAppendChild1 | | A (slightly faster) shortcut for standard domAppendChild function \--------------------------------------------------------------------------*/ static void domAppendChild1 (domNode *node, domNode *childToAppend) { if (node->lastChild) { node->lastChild->nextSibling = childToAppend; childToAppend->previousSibling = node->lastChild; } else { node->firstChild = childToAppend; childToAppend->previousSibling = NULL; } node->lastChild = childToAppend; childToAppend->nextSibling = NULL; childToAppend->parentNode = node; node->ownerDocument->fragments = NULL; } /* EOF $RCSfile $ */ /* Emacs Setup Variables */ /* Local Variables: */ /* mode: C */ /* indent-tabs-mode: nil */ /* c-basic-offset: 4 */ /* End: */