/** * xsltResolveAttrSet: * @set: the attribute set * @asctx: the context for attribute set resolution * @name: the local name of the attirbute set * @ns: the namespace of the attribute set * @depth: recursion depth * * resolve the references in an attribute set. */ static void xsltResolveAttrSet(xsltAttrSetPtr set, xsltStylesheetPtr topStyle, xsltStylesheetPtr style, const xmlChar *name, const xmlChar *ns, int depth) { xsltStylesheetPtr cur; xsltAttrSetPtr other; if (set->state == ATTRSET_RESOLVED) return; if (set->state == ATTRSET_RESOLVING) { xsltTransformError(NULL, topStyle, NULL, "xsl:attribute-set : use-attribute-sets recursion detected" " on %s\n", name); topStyle->errors++; set->state = ATTRSET_RESOLVED; return; } if (depth > 100) { xsltTransformError(NULL, topStyle, NULL, "xsl:attribute-set : use-attribute-sets maximum recursion " "depth exceeded on %s\n", name); topStyle->errors++; return; } set->state = ATTRSET_RESOLVING; xsltResolveUseAttrSets(set, topStyle, depth); /* Merge imported sets. */ cur = xsltNextImport(style); while (cur != NULL) { if (cur->attributeSets != NULL) { other = xmlHashLookup2(cur->attributeSets, name, ns); if (other != NULL) { #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "xsl:attribute-set : merging import for %s\n", name); #endif xsltResolveUseAttrSets(other, topStyle, depth); xsltMergeAttrSets(set, other); xmlHashRemoveEntry2(cur->attributeSets, name, ns, NULL); xsltFreeAttrSet(other); } } cur = xsltNextImport(cur); } set->state = ATTRSET_RESOLVED; }
/** * xsltResolveUseAttrSets: * @set: the attribute set * @asctx: the context for attribute set resolution * @depth: recursion depth * * Process "use-attribute-sets". */ static void xsltResolveUseAttrSets(xsltAttrSetPtr set, xsltStylesheetPtr topStyle, int depth) { xsltStylesheetPtr cur; xsltAttrSetPtr other; xsltUseAttrSetPtr use = set->useAttrSets; xsltUseAttrSetPtr next; while (use != NULL) { /* * Iterate top stylesheet and all imports. */ cur = topStyle; while (cur != NULL) { if (cur->attributeSets) { other = xmlHashLookup2(cur->attributeSets, use->ncname, use->ns); if (other != NULL) { xsltResolveAttrSet(other, topStyle, cur, use->ncname, use->ns, depth + 1); xsltMergeAttrSets(set, other); break; } } cur = xsltNextImport(cur); } next = use->next; /* Free useAttrSets early. */ xsltFreeUseAttrSet(use); use = next; } set->useAttrSets = NULL; }
xsltTemplatePtr xsltFindTemplate(xsltTransformContextPtr ctxt, const xmlChar *name, const xmlChar *nameURI) { xsltTemplatePtr cur; xsltStylesheetPtr style; if ((ctxt == NULL) || (name == NULL)) return(NULL); style = ctxt->style; while (style != NULL) { cur = style->templates; while (cur != NULL) { if (xmlStrEqual(name, cur->name)) { if (((nameURI == NULL) && (cur->nameURI == NULL)) || ((nameURI != NULL) && (cur->nameURI != NULL) && (xmlStrEqual(nameURI, cur->nameURI)))) { return(cur); } } cur = cur->next; } style = xsltNextImport(style); } return(NULL); }
/** * xsltInitCtxtKeys: * @ctxt: an XSLT transformation context * @idoc: a document info * * Computes all the keys tables for the current input document. * Should be done before global varibales are initialized. * NOTE: Not used anymore in the refactored code. */ void xsltInitCtxtKeys(xsltTransformContextPtr ctxt, xsltDocumentPtr idoc) { xsltStylesheetPtr style; xsltKeyDefPtr keyDef; if ((ctxt == NULL) || (idoc == NULL)) return; #ifdef KEY_INIT_DEBUG fprintf(stderr, "xsltInitCtxtKeys on document\n"); #endif #ifdef WITH_XSLT_DEBUG_KEYS if ((idoc->doc != NULL) && (idoc->doc->URL != NULL)) XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext, "Initializing keys on %s\n", idoc->doc->URL)); #endif style = ctxt->style; while (style != NULL) { keyDef = (xsltKeyDefPtr) style->keys; while (keyDef != NULL) { xsltInitCtxtKey(ctxt, idoc, keyDef); keyDef = keyDef->next; } style = xsltNextImport(style); } #ifdef KEY_INIT_DEBUG fprintf(stderr, "xsltInitCtxtKeys on document: done\n"); #endif }
/** * exsltFuncInit: * @ctxt: an XSLT transformation context * @URI: the namespace URI for the extension * * Initializes the EXSLT - Functions module. * Called at transformation-time; merges all * functions declared in the import tree taking * import precedence into account, i.e. overriding * functions with lower import precedence. * * Returns the data for this transformation */ static exsltFuncData * exsltFuncInit (xsltTransformContextPtr ctxt, const xmlChar *URI) { exsltFuncData *ret; xsltStylesheetPtr tmp; exsltFuncImportRegData ch; xmlHashTablePtr hash; ret = (exsltFuncData *) xmlMalloc (sizeof(exsltFuncData)); if (ret == NULL) { xsltGenericError(xsltGenericErrorContext, "exsltFuncInit: not enough memory\n"); return(NULL); } memset(ret, 0, sizeof(exsltFuncData)); ret->result = NULL; ret->error = 0; ch.hash = (xmlHashTablePtr) xsltStyleGetExtData(ctxt->style, URI); ret->funcs = ch.hash; xmlHashScanFull(ch.hash, (xmlHashScannerFull) exsltFuncRegisterFunc, ctxt); tmp = ctxt->style; ch.ctxt = ctxt; while ((tmp=xsltNextImport(tmp))!=NULL) { hash = xsltGetExtInfo(tmp, URI); if (hash != NULL) { xmlHashScanFull(hash, (xmlHashScannerFull) exsltFuncRegisterImportFunc, &ch); } } return(ret); }
void xsltApplyAttributeSet(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst ATTRIBUTE_UNUSED, const xmlChar * attributes) { const xmlChar *ncname = NULL; const xmlChar *prefix = NULL; const xmlChar *attrib, *endattr; xsltAttrElemPtr values; xsltStylesheetPtr style; if (attributes == NULL) { return; } attrib = attributes; while (*attrib != 0) { while (IS_BLANK(*attrib)) attrib++; if (*attrib == 0) break; endattr = attrib; while ((*endattr != 0) && (!IS_BLANK(*endattr))) endattr++; attrib = xmlDictLookup(ctxt->dict, attrib, endattr - attrib); if (attrib) { #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "apply attribute set %s\n", attrib); #endif ncname = xsltSplitQName(ctxt->dict, attrib, &prefix); style = ctxt->style; #ifdef WITH_DEBUGGER if ((style != NULL) && (style->attributeSets != NULL) && (ctxt->debugStatus != XSLT_DEBUG_NONE)) { values = xmlHashLookup2(style->attributeSets, ncname, prefix); if ((values != NULL) && (values->attr != NULL)) xslHandleDebugger(values->attr->parent, node, NULL, ctxt); } #endif while (style != NULL) { values = xmlHashLookup2(style->attributeSets, ncname, prefix); while (values != NULL) { if (values->attr != NULL) { xsltAttributeInternal(ctxt, node, values->attr, values->attr->psvi, 1); } values = values->next; } style = xsltNextImport(style); } } attrib = endattr; } }
/** * xsltInitAllDocKeys: * @ctxt: transformation context * * INTERNAL ROUTINE ONLY * * Check if any keys on the current document need to be computed * * Returns 0 in case of success, -1 in case of failure */ int xsltInitAllDocKeys(xsltTransformContextPtr ctxt) { xsltStylesheetPtr style; xsltKeyDefPtr keyd; xsltKeyTablePtr table; if (ctxt == NULL) return(-1); #ifdef KEY_INIT_DEBUG fprintf(stderr, "xsltInitAllDocKeys %d %d\n", ctxt->document->nbKeysComputed, ctxt->nbKeys); #endif if (ctxt->document->nbKeysComputed == ctxt->nbKeys) return(0); /* * TODO: This could be further optimized */ style = ctxt->style; while (style) { keyd = (xsltKeyDefPtr) style->keys; while (keyd != NULL) { #ifdef KEY_INIT_DEBUG fprintf(stderr, "Init key %s\n", keyd->name); #endif /* * Check if keys with this QName have been already * computed. */ table = (xsltKeyTablePtr) ctxt->document->keys; while (table) { if (((keyd->nameURI != NULL) == (table->nameURI != NULL)) && xmlStrEqual(keyd->name, table->name) && xmlStrEqual(keyd->nameURI, table->nameURI)) { break; } table = table->next; } if (table == NULL) { /* * Keys with this QName have not been yet computed. */ xsltInitDocKeyTable(ctxt, keyd->name, keyd->nameURI); } keyd = keyd->next; } style = xsltNextImport(style); } #ifdef KEY_INIT_DEBUG fprintf(stderr, "xsltInitAllDocKeys: done\n"); #endif return(0); }
/** * xsltGetNamespace: * @ctxt: a transformation context * @cur: the input node * @ns: the namespace * @out: the output node (or its parent) * * Find a matching (prefix and ns-name) ns-declaration * for the requested @ns->prefix and @ns->href in the result tree. * If none is found then a new ns-declaration will be * added to @resultElem. If, in this case, the given prefix is * already in use, then a ns-declaration with a modified ns-prefix * be we created. * * Called by: * - xsltCopyPropList() (*not* anymore) * - xsltShallowCopyElement() * - xsltCopyTreeInternal() (*not* anymore) * - xsltApplySequenceConstructor() (*not* in the refactored code), * - xsltElement() (*not* anymore) * * Returns a namespace declaration or NULL in case of * namespace fixup failures or API or internal errors. */ xmlNsPtr xsltGetNamespace(xsltTransformContextPtr ctxt, xmlNodePtr cur, xmlNsPtr ns, xmlNodePtr out) { if (ns == NULL) return(NULL); #ifdef XSLT_REFACTORED /* * Namespace exclusion and ns-aliasing is performed at * compilation-time in the refactored code. * Additionally, aliasing is not intended for non Literal * Result Elements. */ return(xsltGetSpecialNamespace(ctxt, cur, ns->href, ns->prefix, out)); #else { xsltStylesheetPtr style; const xmlChar *URI = NULL; /* the replacement URI */ if ((ctxt == NULL) || (cur == NULL) || (out == NULL)) return(NULL); style = ctxt->style; while (style != NULL) { if (style->nsAliases != NULL) URI = (const xmlChar *) xmlHashLookup(style->nsAliases, ns->href); if (URI != NULL) break; style = xsltNextImport(style); } if (URI == UNDEFINED_DEFAULT_NS) { return(xsltGetSpecialNamespace(ctxt, cur, NULL, NULL, out)); #if 0 /* * TODO: Removed, since wrong. If there was no default * namespace in the stylesheet then this must resolve to * the NULL namespace. */ xmlNsPtr dflt; dflt = xmlSearchNs(cur->doc, cur, NULL); if (dflt != NULL) URI = dflt->href; else return NULL; #endif } else if (URI == NULL) URI = ns->href; return(xsltGetSpecialNamespace(ctxt, cur, URI, ns->prefix, out)); } #endif }
/** * xsltResolveStylesheetAttributeSet: * @style: the XSLT stylesheet * * resolve the references between attribute sets. */ void xsltResolveStylesheetAttributeSet(xsltStylesheetPtr style) { xsltStylesheetPtr cur; #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "Resolving attribute sets references\n"); #endif /* * First aggregate all the attribute sets definitions from the imports */ cur = xsltNextImport(style); while (cur != NULL) { if (cur->attributeSets != NULL) { if (style->attributeSets == NULL) { #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "creating attribute set table\n"); #endif style->attributeSets = xmlHashCreate(10); } xmlHashScanFull(cur->attributeSets, (xmlHashScannerFull) xsltMergeSASCallback, style); /* * the attribute lists have either been migrated to style * or freed directly in xsltMergeSASCallback() */ xmlHashFree(cur->attributeSets, NULL); cur->attributeSets = NULL; } cur = xsltNextImport(cur); } /* * Then resolve all the references and computes the resulting sets */ if (style->attributeSets != NULL) { xmlHashScanFull(style->attributeSets, (xmlHashScannerFull) xsltResolveSASCallback, style); } }
/** * xsltGetSAS: * @style: the XSLT stylesheet * @name: the attribute list name * @ns: the attribute list namespace * * lookup an attribute set based on the style cascade * * Returns the attribute set or NULL */ static xsltAttrElemPtr xsltGetSAS(xsltStylesheetPtr style, const xmlChar *name, const xmlChar *ns) { xsltAttrElemPtr values; while (style != NULL) { values = xmlHashLookup2(style->attributeSets, name, ns); if (values != NULL) return(values); style = xsltNextImport(style); } return(NULL); }
int xsltNeedElemSpaceHandling(xsltTransformContextPtr ctxt) { xsltStylesheetPtr style; if (ctxt == NULL) return(0); style = ctxt->style; while (style != NULL) { if (style->stripSpaces != NULL) return(1); style = xsltNextImport(style); } return(0); }
/** * xsltInitDocKeyTable: * * INTERNAL ROUTINE ONLY * * Check if any keys on the current document need to be computed */ static int xsltInitDocKeyTable(xsltTransformContextPtr ctxt, const xmlChar *name, const xmlChar *nameURI) { xsltStylesheetPtr style; xsltKeyDefPtr keyd = NULL; int found = 0; #ifdef KEY_INIT_DEBUG fprintf(stderr, "xsltInitDocKeyTable %s\n", name); #endif style = ctxt->style; while (style != NULL) { keyd = (xsltKeyDefPtr) style->keys; while (keyd != NULL) { if (((keyd->nameURI != NULL) == (nameURI != NULL)) && xmlStrEqual(keyd->name, name) && xmlStrEqual(keyd->nameURI, nameURI)) { xsltInitCtxtKey(ctxt, ctxt->document, keyd); if (ctxt->document->nbKeysComputed == ctxt->nbKeys) return(0); found = 1; } keyd = keyd->next; } style = xsltNextImport(style); } if (found == 0) { #ifdef WITH_XSLT_DEBUG_KEYS XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext, "xsltInitDocKeyTable: did not found %s\n", name)); #endif xsltTransformError(ctxt, NULL, keyd? keyd->inst : NULL, "Failed to find key definition for %s\n", name); ctxt->state = XSLT_STATE_STOPPED; return(-1); } #ifdef KEY_INIT_DEBUG fprintf(stderr, "xsltInitDocKeyTable %s done\n", name); #endif return(0); }
/** * xsltGetNamespace: * @ctxt: a transformation context * @cur: the input node * @ns: the namespace * @out: the output node (or its parent) * * Find the right namespace value for this prefix, if needed create * and add a new namespace decalaration on the node * Handle namespace aliases * * Returns the namespace node to use or NULL */ xmlNsPtr xsltGetNamespace(xsltTransformContextPtr ctxt, xmlNodePtr cur, xmlNsPtr ns, xmlNodePtr out) { xsltStylesheetPtr style; xmlNsPtr ret; const xmlChar *URI = NULL; /* the replacement URI */ if ((ctxt == NULL) || (cur == NULL) || (out == NULL) || (ns == NULL)) return(NULL); style = ctxt->style; while (style != NULL) { if (style->nsAliases != NULL) URI = (const xmlChar *) xmlHashLookup(ctxt->style->nsAliases, ns->href); if (URI != NULL) break; style = xsltNextImport(style); } if (URI == NULL) URI = ns->href; if ((out->parent != NULL) && (out->parent->type == XML_ELEMENT_NODE) && (out->parent->ns != NULL) && (xmlStrEqual(out->parent->ns->href, URI))) ret = out->parent->ns; else ret = xmlSearchNsByHref(out->doc, out, URI); if (ret == NULL) { if (out->type == XML_ELEMENT_NODE) ret = xmlNewNs(out, URI, ns->prefix); } return(ret); }
int xsltFindElemSpaceHandling(xsltTransformContextPtr ctxt, xmlNodePtr node) { xsltStylesheetPtr style; const xmlChar *val; if ((ctxt == NULL) || (node == NULL)) return(0); style = ctxt->style; while (style != NULL) { if (node->ns != NULL) { val = (const xmlChar *) xmlHashLookup2(style->stripSpaces, node->name, node->ns->href); if (val == NULL) { val = (const xmlChar *) xmlHashLookup2(style->stripSpaces, BAD_CAST "*", node->ns->href); } } else { val = (const xmlChar *) xmlHashLookup2(style->stripSpaces, node->name, NULL); } if (val != NULL) { if (xmlStrEqual(val, (xmlChar *) "strip")) return(1); if (xmlStrEqual(val, (xmlChar *) "preserve")) return(0); } if (style->stripAll == 1) return(1); if (style->stripAll == -1) return(0); style = xsltNextImport(style); } return(0); }
/** * xsltResolveStylesheetAttributeSet: * @style: the XSLT stylesheet * * resolve the references between attribute sets. */ void xsltResolveStylesheetAttributeSet(xsltStylesheetPtr style) { xsltStylesheetPtr cur; xsltAttrSetContext asctx; #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "Resolving attribute sets references\n"); #endif asctx.topStyle = style; cur = style; while (cur != NULL) { if (cur->attributeSets != NULL) { if (style->attributeSets == NULL) { #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "creating attribute set table\n"); #endif style->attributeSets = xmlHashCreate(10); } asctx.style = cur; xmlHashScanFull(cur->attributeSets, (xmlHashScannerFull) xsltResolveSASCallback, &asctx); if (cur != style) { /* * the attribute lists have either been migrated to style * or freed directly in xsltResolveSASCallback() */ xmlHashFree(cur->attributeSets, NULL); cur->attributeSets = NULL; } } cur = xsltNextImport(cur); } }
/** * xsltInitCtxtKeys: * @ctxt: an XSLT transformation context * @doc: an XSLT document * * Computes all the keys tables for the current input document. * Should be done before global varibales are initialized. * NOTE: Not used anymore in the refactored code. */ void xsltInitCtxtKeys(xsltTransformContextPtr ctxt, xsltDocumentPtr doc) { xsltStylesheetPtr style; xsltKeyDefPtr keyd; if ((ctxt == NULL) || (doc == NULL)) return; #ifdef WITH_XSLT_DEBUG_KEYS if ((doc->doc != NULL) && (doc->doc->URL != NULL)) XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext, "Initializing keys on %s\n", doc->doc->URL)); #endif style = ctxt->style; while (style != NULL) { keyd = (xsltKeyDefPtr) style->keys; while (keyd != NULL) { xsltInitCtxtKey(ctxt, doc, keyd); keyd = keyd->next; } style = xsltNextImport(style); } }
/** * xsltGetKey: * @ctxt: an XSLT transformation context * @name: the key name or NULL * @nameURI: the name URI or NULL * @value: the key value to look for * * Lookup a key * * Returns the nodeset resulting from the query or NULL */ xmlNodeSetPtr xsltGetKey(xsltTransformContextPtr ctxt, const xmlChar *name, const xmlChar *nameURI, const xmlChar *value) { xmlNodeSetPtr ret; xsltKeyTablePtr table; #ifdef XSLT_REFACTORED_KEYCOMP int found = 0; #endif if ((ctxt == NULL) || (name == NULL) || (value == NULL) || (ctxt->document == NULL)) return(NULL); #ifdef WITH_XSLT_DEBUG_KEYS xsltGenericDebug(xsltGenericDebugContext, "Get key %s, value %s\n", name, value); #endif table = (xsltKeyTablePtr) ctxt->document->keys; while (table != NULL) { if (((nameURI != NULL) == (table->nameURI != NULL)) && xmlStrEqual(table->name, name) && xmlStrEqual(table->nameURI, nameURI)) { #ifdef XSLT_REFACTORED_KEYCOMP found = 1; #endif ret = (xmlNodeSetPtr)xmlHashLookup(table->keys, value); return(ret); } table = table->next; } #ifdef XSLT_REFACTORED_KEYCOMP if (! found) { xsltStylesheetPtr style = ctxt->style; xsltKeyDefPtr keyd; /* * This might be the first call to the key with the specified * name and the specified document. * Find all keys with a matching name and compute them for the * current tree. */ found = 0; while (style != NULL) { keyd = (xsltKeyDefPtr) style->keys; while (keyd != NULL) { if (((nameURI != NULL) == (keyd->nameURI != NULL)) && xmlStrEqual(keyd->name, name) && xmlStrEqual(keyd->nameURI, nameURI)) { found = 1; xsltInitCtxtKey(ctxt, ctxt->document, keyd); } keyd = keyd->next; } style = xsltNextImport(style); } if (found) { /* * The key was computed, so look it up. */ table = (xsltKeyTablePtr) ctxt->document->keys; while (table != NULL) { if (((nameURI != NULL) == (table->nameURI != NULL)) && xmlStrEqual(table->name, name) && xmlStrEqual(table->nameURI, nameURI)) { ret = (xmlNodeSetPtr)xmlHashLookup(table->keys, value); return(ret); } table = table->next; } } } #endif return(NULL); }
/** * xsltApplyAttributeSet: * @ctxt: the XSLT stylesheet * @node: the node in the source tree. * @inst: the attribute node "xsl:use-attribute-sets" * @attrSets: the list of QNames of the attribute-sets to be applied * * Apply the xsl:use-attribute-sets. * If @attrSets is NULL, then @inst will be used to exctract this * value. * If both, @attrSets and @inst, are NULL, then this will do nothing. */ void xsltApplyAttributeSet(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, const xmlChar *attrSets) { const xmlChar *ncname = NULL; const xmlChar *prefix = NULL; const xmlChar *curstr, *endstr; xsltAttrElemPtr attrs; xsltStylesheetPtr style; if (attrSets == NULL) { if (inst == NULL) return; else { /* * Extract the value from @inst. */ if (inst->type == XML_ATTRIBUTE_NODE) { if ( ((xmlAttrPtr) inst)->children != NULL) attrSets = ((xmlAttrPtr) inst)->children->content; } if (attrSets == NULL) { /* * TODO: Return an error? */ return; } } } /* * Parse/apply the list of QNames. */ curstr = attrSets; while (*curstr != 0) { while (IS_BLANK(*curstr)) curstr++; if (*curstr == 0) break; endstr = curstr; while ((*endstr != 0) && (!IS_BLANK(*endstr))) endstr++; curstr = xmlDictLookup(ctxt->dict, curstr, endstr - curstr); if (curstr) { /* * TODO: Validate the QName. */ #ifdef WITH_XSLT_DEBUG_curstrUTES xsltGenericDebug(xsltGenericDebugContext, "apply curstrute set %s\n", curstr); #endif ncname = xsltSplitQName(ctxt->dict, curstr, &prefix); style = ctxt->style; #ifdef WITH_DEBUGGER if ((style != NULL) && (style->attributeSets != NULL) && (ctxt->debugStatus != XSLT_DEBUG_NONE)) { attrs = xmlHashLookup2(style->attributeSets, ncname, prefix); if ((attrs != NULL) && (attrs->attr != NULL)) xslHandleDebugger(attrs->attr->parent, node, NULL, ctxt); } #endif /* * Lookup the referenced curstrute-set. */ while (style != NULL) { attrs = xmlHashLookup2(style->attributeSets, ncname, prefix); while (attrs != NULL) { if (attrs->attr != NULL) { xsltAttributeInternal(ctxt, node, attrs->attr, attrs->attr->psvi, 1); } attrs = attrs->next; } style = xsltNextImport(style); } } curstr = endstr; } }