/*
* Copyright (C) 2012, Antonin Houska
*/
#include "xmlnode.h"
#include "xml_parser.h"
#include "xmlnode_util.h"
#include "xnt.h"
/*
* 1. Order must exactly follow that of XNT nodes in 'XMLNodeKind' enumeration.
* 2. For each element the required attributes must be at lower positions than
* the optional. (This restriction only applies to 'xmlAttributInfo', not to
* the actual input document.)
*/
XNTAttrNames xntAttributeInfo[] = {
/* XNTNODE_TEMPLATE */
{1, {"preserve-space"}, {false}},
/* XNTNODE_COPY_OF */
{1, {"expr"}, {true}},
/* XNTNODE_ELEMENT */
{1, {"name"}, {true}},
/* XNTNODE_ATTRIBUTE */
{2, {"name", "value"}, {true, true}}
};
static int paramNameComparator(const void *left, const void *right);
static XPathExpression getXPathExpression(char *src, unsigned int termFlags, XMLNodeContainer paramNames,
unsigned short *endPos);
static char *getAttrValueTokenized(char *attrValue, unsigned int *valueSize, XMLNodeContainer paramNames);
static void visitXMLNodeForValidation(XMLNodeHdr *stack, unsigned int depth, void *userData);
static Datum castParameterValue(Datum value, Oid sourceType, Oid targetType);
static void buildNewNodeTree(XMLNodeHdr node, XNodeInternal parent, unsigned int *storageSize,
XPathExprOperandValue paramValues, unsigned short *paramMap, XPathExprState exprState,
bool preserveSpace);
static XMLNodeHdr getNewAttribute(char *name, uint8 flags, char *value, char decideOnDelim, unsigned int *size);
static XNodeInternal getInternalNode(XMLNodeHdr node, bool copy);
static void freeTemplateTree(XNodeInternal root);
static XMLCompNodeHdr getTemplateFromDoc(XMLCompNodeHdr docRoot);
static XPathExpression substituteParameters(XPathExprState exprState, XPathExpression expression,
XPathExprOperandValue paramValues, unsigned short *paramMap);
static bool whitespacesOnly(char *str);
PG_FUNCTION_INFO_V1(xnode_template_in);
Datum
xnode_template_in(PG_FUNCTION_ARGS)
{
pg_enc dbEnc;
XMLNodeParserStateData parserState;
char *input = PG_GETARG_CSTRING(0);
XMLNodeOffset *rootOffPtr;
XMLNodeHdr root;
if (strlen(input) == 0)
{
elog(ERROR, "zero length input string");
}
dbEnc = GetDatabaseEncoding();
if (dbEnc != PG_UTF8)
{
elog(ERROR, "The current version of xmlnode requires both database encoding to be UTF-8.");
}
initXMLParserState(&parserState, input, XNTNODE_ROOT, getXNTNodeKind);
xmlnodeParseDoc(&parserState);
rootOffPtr = (XMLNodeOffset *) (parserState.tree + parserState.dstPos - sizeof(XMLNodeOffset));
root = (XMLNodeHdr) (parserState.tree + *rootOffPtr);
Assert(root->kind == XNTNODE_ROOT);
validateXNTTree(root);
finalizeXMLParserState(&parserState);
PG_RETURN_POINTER(parserState.result);
}
PG_FUNCTION_INFO_V1(xnode_template_out);
Datum
xnode_template_out(PG_FUNCTION_ARGS)
{
xnt template = (xnt) PG_GETARG_VARLENA_P(0);
char *data = (char *) VARDATA(template);
XMLNodeOffset rootNdOff = XNODE_ROOT_OFFSET(template);
PG_RETURN_CSTRING(dumpXMLNode(data, rootNdOff, VARSIZE(template)));
}
XMLNodeKind
getXNTNodeKind(char *name)
{
unsigned int xntNmspPrefLen = strlen(XNTNODE_NAMESPACE_PREFIX);
Assert(strncmp(name, XNTNODE_NAMESPACE_PREFIX, xntNmspPrefLen) == 0 && name[xntNmspPrefLen] == XNODE_CHAR_COLON);
/* skip the 'xnt:' */
name += xntNmspPrefLen + 1;
if (strcmp(name, XNT_TEMPLATE) == 0)
{
return XNTNODE_TEMPLATE;
}
else if (strcmp(name, XNT_COPY_OF) == 0)
{
return XNTNODE_COPY_OF;
}
else if (strcmp(name, XNT_ELEMENT) == 0)
{
return XNTNODE_ELEMENT;
}
else if (strcmp(name, XNT_ATTRIBUTE) == 0)
{
return XNTNODE_ATTRIBUTE;
}
elog(ERROR, "unrecognized xnt node '%s'", name);
return 0;
}
char *
getXNTNodeName(XMLNodeKind kind)
{
StringInfoData out;
char *name;
xnodeInitStringInfo(&out, 32);
appendStringInfo(&out, "%s:", XNTNODE_NAMESPACE_PREFIX);
switch (kind)
{
case XNTNODE_TEMPLATE:
name = XNT_TEMPLATE;
break;
case XNTNODE_COPY_OF:
name = XNT_COPY_OF;
break;
case XNTNODE_ELEMENT:
name = XNT_ELEMENT;
break;
case XNTNODE_ATTRIBUTE:
name = XNT_ATTRIBUTE;
break;
default:
elog(ERROR, "unrecognized xnt node kind %u", kind);
return NULL;
}
appendStringInfoString(&out, name);
return out.data;
}
/*
* Returns name of special (reserved) attribute.
* Name of such an attribute is determined by position in the array of
* children. The name is not stored in the document tree.
*/
char *
getXNTAttributeName(XMLNodeKind kind, unsigned short attrNr)
{
XNTAttrNames *attrInfo = xntAttributeInfo + (kind - XNTNODE_TEMPLATE);
if (attrNr >= attrInfo->number)
{
return NULL;
}
return attrInfo->names[attrNr];
}
void
validateXNTTree(XMLNodeHdr root)
{
bool hasRootTemplate = false;
walkThroughXMLTree(root, visitXMLNodeForValidation, false, (void *) &hasRootTemplate);
if (!hasRootTemplate)
{
elog(ERROR, "'xnt:template' must be the root element");
}
}
/*
* Ensure that:
*
* 1. Reserved attribute are stored at defined positions.
* 2. Name is not stored for the reserved attributes.
* 3. Where appropriate, the attribute value is stored in special binary format
* as opposed to NULL-terminated string.
*
* 'attrOffsets' - both input and output. It's expected to provide offsets of
* attributes to be checked. Offsets of the processed attributes are returned.
* Therefore there has to be sufficient space for slots for optional attributes
* that the input document does not contain.
* 'attrCount + XNT_SPECIAL_ATTRS_MAX' is always the safe size.
*
* 'attrCount' - the actual number of attributes that the template element contains.
*
* 'offsetsValid' - if given attribute is optional and the input element does not
* contain it, then the corresponding position in 'attrOfsets' will be marked
* as invalid. Size must be equal to that of 'attrOffsets'.
*
*/
char *
preprocessXNTAttributes(XNodeListItem *attrOffsets, unsigned short attrCount, char *parserOutput,
XMLNodeKind specNodeKind, bool *offsetsValid, unsigned int *specAttrCount, unsigned int *outSize,
unsigned int *outCount, XMLNodeContainer paramNames)
{
unsigned short i;
XNTAttrNames *attrInfo = xntAttributeInfo + (specNodeKind - XNTNODE_TEMPLATE);
XMLNodeHdr *attrsSorted = NULL;
/*
* Space for the reserved attributes must always be there, even if all
* attributes of the given element are optional and user does not use any
* (namespace declarations are always allowed so the corner case is that
* user only specifies namespace declarations and nothing else).
*/
unsigned int outAttrsMax = attrCount + attrInfo->number;
bool *attrValsToFree = NULL;
char *resTmp,
*result = NULL;
unsigned int *attrSizes = NULL;
unsigned int indGen;
unsigned int outSizeMax = 0;
*outCount = 0;
*specAttrCount = attrInfo->number;
if (outAttrsMax > 0)
{
unsigned int size;
size = outAttrsMax * sizeof(bool);;
attrValsToFree = (bool *) palloc(size);
memset(attrValsToFree, false, size);
size = outAttrsMax * sizeof(unsigned int);
attrSizes = (unsigned int *) palloc(size);
memset(attrSizes, 0, size);
size = outAttrsMax * sizeof(XMLNodeHdr);
attrsSorted = (XMLNodeHdr *) palloc(size);
memset(attrsSorted, 0, size);
}
/*
* 'attrsSorted' will start with the special (reserved) attrs, while those
* 'generic' will follow.
*/
indGen = attrInfo->number;
/*
* Sort the attributes, i.e. put the reserved into the slots at the start
* of 'attrsSorted'.
*/
for (i = 0; i < attrCount; i++)
{
unsigned short j;
XNodeListItem *attrItem = attrOffsets + i;
XMLNodeHdr attr = (XMLNodeHdr) (parserOutput + attrItem->value.singleOff);
char *attrName = XNODE_CONTENT(attr);
char *attrValue = attrName + strlen(attrName) + 1;
bool found = false;
/* Is this a reserved attribute? */
for (j = 0; j < attrInfo->number; j++)
{
if (strcmp(attrName, attrInfo->names[j]) == 0)
{
XMLNodeHdr specNode;
char *specNodeValue;
unsigned int valueSize,
newAttrSize;
XPathExpression expr = NULL;
char *attrValueTokenized = NULL;
if (specNodeKind == XNTNODE_COPY_OF && strcmp(attrName, attrInfo->names[XNT_COPY_OF_EXPR]) == 0)
{
expr = getXPathExpression(attrValue, XPATH_TERM_NULL, paramNames, NULL);
valueSize = expr->common.size;
}
else if (strlen(attrValue) > 0 && strchr(attrValue, XNODE_CHAR_LBRKT_CUR) != NULL)
{
attrValueTokenized = getAttrValueTokenized(attrValue, &valueSize, paramNames);
}
else
{
/* Plain string or empty attribute. */
valueSize = strlen(attrValue) + 1;
}
newAttrSize = sizeof(XMLNodeHdrData) + valueSize;
specNode = (XMLNodeHdr) palloc(newAttrSize);
specNode->kind = attr->kind;
specNode->flags = attr->flags;
/* There's no need to store special attribute's name. */
specNodeValue = XNODE_CONTENT(specNode);
if (expr != NULL)
{
/*
* 'expr' is now treated as as binary stream (no access to
* the structures) so we can forget about alignment for a
* while. All we need to know is where the data start in
* the new node. This position will control the alignment
* in the resulting document.
*/
memcpy(specNodeValue, expr, valueSize);
pfree(expr);
specNode->flags |= XNODE_ATTR_VALUE_BINARY;
}
else if (attrValueTokenized != NULL)
{
/* Likewise, see the comment above. */
memcpy(specNodeValue, attrValueTokenized, valueSize);
pfree(attrValueTokenized);
specNode->flags |= XNODE_ATTR_VALUE_BINARY;
}
else
{
strcpy(specNodeValue, attrValue);
}
attrsSorted[j] = specNode;
outSizeMax += newAttrSize;
if (expr != NULL || attrValueTokenized != NULL)
{
outSizeMax += MAX_PADDING(XPATH_ALIGNOF_EXPR);
}
attrSizes[j] = newAttrSize;
attrValsToFree[j] = true;
found = true;
break;
}
}
if (!found)
{
unsigned int nmspDefPrefLen;
/* Namespace declaration is the only generic attribute allowed. */
if ((attr->flags & XNODE_NMSP_PREFIX) &&
strncmp(attrName, XNODE_NAMESPACE_DEF_PREFIX,
(nmspDefPrefLen = strlen(XNODE_NAMESPACE_DEF_PREFIX))) == 0 &&
(attrName[nmspDefPrefLen] == XNODE_CHAR_COLON || attrName[nmspDefPrefLen] == '\0'))
{
attrValue = attrName + strlen(attrName) + 1;
/* The whole attribute is stored in this case. */
attrsSorted[indGen] = attr;
attrSizes[indGen] = sizeof(XMLNodeHdrData) + strlen(attrName) +strlen(attrValue) + 2;
outSizeMax += attrSizes[indGen];
indGen++;
}
else
{
elog(ERROR, "element '%s' does not accept attribute '%s'", getXNTNodeName(specNodeKind), attrName);
}
}
}
/* Check for missing required attributes */
for (i = 0; i < attrInfo->number; i++)
{
if (attrInfo->required[i] && (i >= attrCount || attrsSorted[i] == NULL))
{
elog(ERROR, "required attribute '%s' missing in element '%s'", attrInfo->names[i],
getXNTNodeName(specNodeKind));
}
}
if (outAttrsMax > 0)
{
XNodeListItem *attrItem = attrOffsets;
/*
* The first offset does not change so we can use it as initial new
* value.
*/
XMLNodeOffset offNew = attrItem->value.singleOff;
result = resTmp = (char *) palloc(outSizeMax);
/*
* Construct the new sequence of attributes and adjust parent's
* offsets.
*/
for (i = 0; i < indGen; i++)
{
unsigned int currentSize = attrSizes[i];
if (currentSize > 0)
{
char *ptr,
*ptrAligned;
unsigned int padding = 0;
XMLNodeHdr attribute;
Assert(attrsSorted[i] != NULL);
attribute = attrsSorted[i];
if (attribute->flags & XNODE_ATTR_VALUE_BINARY)
{
/*
* If the next node is located immediately after the
* previous, then 'ptr' is the position inside the new
* node controlling the alignment (typically XPath
* expression). As the 'binary nodes' have the
* alignment-sensitive data right after the header, the
* header size is used to derive the position.
*/
ptr = resTmp + sizeof(XMLNodeHdrData);
/*
* If that address is not aligned, padding must be
* prepended.
*/
ptrAligned = (char *) TYPEALIGN(XPATH_ALIGNOF_EXPR, ptr);
padding = ptrAligned - ptr;
}
resTmp += padding;
memcpy(resTmp, attrsSorted[i], currentSize);
resTmp += currentSize;
offNew += padding;
offsetsValid[i] = true;
}
else
{
Assert(attrsSorted[i] == NULL);
offsetsValid[i] = false;
}
attrItem->value.singleOff = offNew;
offNew += currentSize;
attrItem++;
}
*outCount = indGen;
*outSize = resTmp - result;
for (i = 0; i < outAttrsMax; i++)
{
if (attrValsToFree[i])
{
pfree(attrsSorted[i]);
}
}
pfree(attrValsToFree);
pfree(attrSizes);
pfree(attrsSorted);
}
return result;
}
/*
* Attributes of 'ordinary' elements (i.e. those having neither 'xnt' nor 'xmlns' prefix) might reference parameters.
* For example: '
*/
char *
preprocessXNTAttrValues(XNodeListItem *attrOffsets, unsigned short attrCount, char *parserOutput,
unsigned int *outSize, XMLNodeContainer paramNames)
{
unsigned short i;
bool workToDo = false;
XMLNodeHdr *attrNodes = (XMLNodeHdr *) palloc(attrCount * sizeof(XMLNodeHdr));
unsigned int arrSize;
bool *toReplace;
unsigned int *valueSizes,
*fixedSizes;
char **valuesNew = NULL;
char *result = NULL;
char *resCursor;
XNodeListItem *attrItem;
XMLNodeOffset offNew;
unsigned int outSizeMax = 0;
arrSize = attrCount * sizeof(bool);
toReplace = (bool *) palloc(arrSize);
memset(toReplace, false, arrSize);
arrSize = attrCount * sizeof(unsigned int);
valueSizes = (unsigned int *) palloc(arrSize);
fixedSizes = (unsigned int *) palloc(arrSize);;
arrSize = attrCount * sizeof(char *);
valuesNew = (char **) palloc(arrSize);
memset(valuesNew, 0, arrSize);
for (i = 0; i < attrCount; i++)
{
XNodeListItem *attrItem = attrOffsets + i;
XMLNodeHdr attr = (XMLNodeHdr) (parserOutput + attrItem->value.singleOff);
char *attrName = XNODE_CONTENT(attr);
char *attrValue;
attrNodes[i] = attr;
attrValue = attrName + strlen(attrName) + 1;
if (strlen(attrValue) > 0 && strchr(attrValue, XNODE_CHAR_LBRKT_CUR) != NULL)
{
toReplace[i] = true;
if (!workToDo)
{
workToDo = true;
}
}
fixedSizes[i] = sizeof(XMLNodeHdrData) + strlen(attrName) +1;
valueSizes[i] = strlen(attrValue) + 1;
valuesNew[i] = attrValue;
}
if (!workToDo)
{
pfree(attrNodes);
pfree(toReplace);
pfree(fixedSizes);
pfree(valueSizes);
pfree(valuesNew);
return NULL;
}
for (i = 0; i < attrCount; i++)
{
if (toReplace[i])
{
XMLNodeHdr attrOrig = attrNodes[i];
char *attrName = XNODE_CONTENT(attrOrig);
char *attrValue = attrName + strlen(attrName) + 1;
unsigned int valueSizeNew = 0;
valuesNew[i] = getAttrValueTokenized(attrValue, &valueSizeNew, paramNames);
valueSizes[i] = valueSizeNew;
outSizeMax += MAX_PADDING(XPATH_ALIGNOF_EXPR);
}
outSizeMax += fixedSizes[i] + valueSizes[i];
}
result = resCursor = (char *) palloc(outSizeMax);
attrItem = attrOffsets;
offNew = attrItem->value.singleOff;
for (i = 0; i < attrCount; i++)
{
XMLNodeHdr attr = attrNodes[i];
XMLNodeHdr attrNew;
char *ptr,
*ptrAligned;
unsigned int padding = 0;
if (toReplace[i])
{
/* See preprocessXNTAttributes() for explanation. */
ptr = resCursor + fixedSizes[i];
ptrAligned = (char *) TYPEALIGN(XPATH_ALIGNOF_EXPR, ptr);
padding = ptrAligned - ptr;
}
/*
* Header and attribute name is always copied from the original
* attribute.
*/
resCursor += padding;
memcpy(resCursor, attr, fixedSizes[i]);
attrNew = (XMLNodeHdr) resCursor;
resCursor += fixedSizes[i];
offNew += padding;
/*
* The value is either modified (binary) or the original (plain
* string).
*/
if (toReplace[i])
{
attrNew->flags |= XNODE_ATTR_VALUE_BINARY;
memcpy(resCursor, valuesNew[i], valueSizes[i]);
pfree(valuesNew[i]);
}
else
{
strcpy(resCursor, valuesNew[i]);
}
resCursor += valueSizes[i];
attrItem->value.singleOff = offNew;
offNew += fixedSizes[i] + valueSizes[i];
attrItem++;
}
*outSize = resCursor - result;
pfree(attrNodes);
pfree(toReplace);
pfree(fixedSizes);
pfree(valueSizes);
pfree(valuesNew);
return result;
}
/*
* If 'paramNames' is not-NULL, then 'paramValues', 'paramMap' and 'exprState' must be passed
* and the function evaluates each expression that the attribute value contains and returns
* the value as string.
*
* If 'exprState' is NULL then the function returns source text of the contained expression(s).
* Otherwise it uses 'exprState' to evaluate the expression(s).
*/
char *
dumpBinaryAttrValue(char *binValue, char **paramNames, XPathExprOperandValue paramValues,
unsigned short *paramMap, XPathExprState exprState)
{
StringInfoData output;
char *cursor = binValue;
unsigned short tokenCount,
i;
tokenCount = *((uint8 *) cursor);
Assert(tokenCount > 0);
xnodeInitStringInfo(&output, 32);
cursor++;
for (i = 0; i < tokenCount; i++)
{
bool isExpr = *((bool *) cursor++);
if (isExpr)
{
XPathExpression expr;
cursor = (char *) TYPEALIGN(XPATH_ALIGNOF_EXPR, cursor);
expr = (XPathExpression) cursor;
if (exprState == NULL)
{
appendStringInfoChar(&output, XNODE_CHAR_LBRKT_CUR);
dumpXPathExpression(expr, NULL, &output, true, paramNames, false);
appendStringInfoChar(&output, XNODE_CHAR_RBRKT_CUR);
}
else
{
XPathExprOperandValueData result,
resultCast;
XPathExpression exprCopy;
exprCopy = substituteParameters(exprState, expr, paramValues, paramMap);
evaluateXPathExpression(exprState, exprCopy, 0, &result);
if (result.type == XPATH_VAL_NODESET)
{
elog(ERROR, "node-set is not expected in attribute value");
}
if (!result.isNull)
{
char *valueStr;
castXPathExprOperandToStr(exprState, &result, &resultCast);
valueStr = (char *) getXPathOperandValue(exprState, resultCast.v.stringId, XPATH_VAR_STRING);
appendStringInfoString(&output, valueStr);
}
pfree(exprCopy);
}
cursor += expr->common.size;
}
else
{
appendStringInfoString(&output, cursor);
cursor += strlen(cursor) + 1;
}
}
return output.data;
}
static int
paramNameComparator(const void *left, const void *right)
{
return strcmp(((XNTParamNameSorted *) left)->name, ((XNTParamNameSorted *) right)->name);
}
static XPathExpression
getXPathExpression(char *src, unsigned int termFlags, XMLNodeContainer paramNames,
unsigned short *endPos)
{
XPathExpression expr = (XPathExpression) palloc(XPATH_EXPR_BUFFER_SIZE);
XPathOffset outPos = 0;
XPathParserStateData state;
expr->needsContext = false;
state.c = src;
state.cWidth = 0;
state.pos = 0;
parseXPathExpression(expr, &state, termFlags, NULL, (char *) expr, &outPos, false, false, NULL, NULL,
paramNames);
if (expr->needsContext)
{
elog(ERROR, "one or more operands of the XPath expression require context");
}
if (endPos != NULL)
{
*endPos = state.pos;
}
return expr;
}
static char *
getAttrValueTokenized(char *attrValue, unsigned int *valueSize, XMLNodeContainer paramNames)
{
unsigned short tokenCount = 0;
char *tokens[XNT_ATTR_VALUE_MAX_TOKENS];
bool tokenIsExpr[XNT_ATTR_VALUE_MAX_TOKENS];
unsigned int tokenSizes[XNT_ATTR_VALUE_MAX_TOKENS];
char *c = attrValue;
unsigned short k;
char *result,
*resCursor;
unsigned int valueSizeMax = 0;
/*
* Attribute value contains xpath expressions and therefore must be
* tokenized.
*/
/* Each iteration processes 1 token. */
while (*c != '\0')
{
if (tokenCount == XNT_ATTR_VALUE_MAX_TOKENS)
{
elog(ERROR, "xnt attribute value must not consist of more than %u tokens",
XNT_ATTR_VALUE_MAX_TOKENS);
}
if (*c == XNODE_CHAR_LBRKT_CUR)
{
XPathExpression tokenExpr;
unsigned short endPos = 0;
c++; /* Skip the left curly bracket. */
/* Parse the xpath expression, ending with '}' */
tokenExpr = getXPathExpression(c, XPATH_TERM_RBRKT_CRL, paramNames, &endPos);
tokens[tokenCount] = (char *) tokenExpr;
tokenSizes[tokenCount] = tokenExpr->common.size;
tokenIsExpr[tokenCount] = true;
tokenCount++;
valueSizeMax += MAX_PADDING(XPATH_ALIGNOF_EXPR) + tokenExpr->common.size;
c += endPos;
}
else
{
char *tokStart = c;
unsigned int tokLen = 0;
/* plain string */
while (*c != '\0' && *c != XNODE_CHAR_LBRKT_CUR)
{
unsigned int cWidth = pg_utf_mblen((unsigned char *) c);
c += cWidth;
tokLen += cWidth;
}
if (tokLen > 0)
{
unsigned int size = tokLen + 1;
char *token = (char *) palloc(size);
memcpy(token, tokStart, tokLen);
token[tokLen] = '\0';
tokens[tokenCount] = token;
tokenSizes[tokenCount] = size;
tokenIsExpr[tokenCount] = false;
tokenCount++;
valueSizeMax += size;
}
}
}
Assert(tokenCount > 0);
/* Add space for the total number of tokens as well as type of each token. */
valueSizeMax += tokenCount * sizeof(bool) + sizeof(uint8);
/* Construct the tokenized binary value. */
result = resCursor = (char *) palloc(valueSizeMax);
*((uint8 *) resCursor) = tokenCount;
resCursor++;
for (k = 0; k < tokenCount; k++)
{
bool isExpression = tokenIsExpr[k];
*((bool *) resCursor) = isExpression;
resCursor++;
if (isExpression)
{
resCursor = (char *) TYPEALIGN(XPATH_ALIGNOF_EXPR, resCursor);
}
memcpy(resCursor, tokens[k], tokenSizes[k]);
resCursor += tokenSizes[k];
pfree(tokens[k]);
}
*valueSize = resCursor - result;
return result;
}
static void
visitXMLNodeForValidation(XMLNodeHdr *stack, unsigned int depth, void *userData)
{
XMLNodeHdr node = stack[depth];
XMLNodeHdr parent = NULL;
if ((node->flags & XNODE_EL_SPECIAL) == 0)
{
return;
}
if (depth >= 1)
{
parent = stack[depth - 1];
}
switch (node->kind)
{
case XNTNODE_ROOT:
Assert(depth == 0);
break;
case XNTNODE_TEMPLATE:
if (depth == 1)
{
bool *hasRootTemplate = (bool *) userData;
if (node->flags & XNODE_EMPTY)
{
elog(ERROR, "root template must not be empty");
}
*hasRootTemplate = true;
return;
}
else
{
elog(ERROR, "'xnt:template' element is only allowed as the root element");
}
break;
case XNTNODE_COPY_OF:
if (!(node->flags & XNODE_EMPTY))
{
elog(ERROR, "'xnt:copy-of' element must be empty");
}
break;
case XNTNODE_ELEMENT:
break;
case XNTNODE_ATTRIBUTE:
if (!(parent != NULL && parent->kind == XNTNODE_ELEMENT))
{
elog(ERROR, "'xnt:element' must be parent of 'xnt:attribute'");
}
if (!(node->flags & XNODE_EMPTY))
{
/*
* It doesn't seem to bring additional value if node content
* could represent the attribute value, for example:
*
* 1
*/
elog(ERROR, "'xnt:attribute' element must be empty");
}
break;
default:
elog(ERROR, "unrecognized node kind %u found during validation", node->kind);
break;
}
}
PG_FUNCTION_INFO_V1(xnode_from_template);
Datum
xnode_from_template(PG_FUNCTION_ARGS)
{
xnt template;
XMLCompNodeHdr templDocRoot,
templNode;
char *templHdrData,
*templData;
char **templParNames = NULL;
XNTHeader templHdr;
ArrayType *parNameArr;
int parNameCount = 0;
XNTParamNameSorted *parNames = NULL;
unsigned int substNodeCount = 0;
XMLNodeOffset *substNodes = NULL;
Datum row;
HeapTupleHeader tupHdr;
Oid tupType;
int32 tupTypmod;
TupleDesc tupDesc;
unsigned int i;
HeapTupleData tmpTup,
*tup;
XPathExprOperandValue parValues = NULL;
unsigned short *paramMap = NULL;
Form_pg_proc procStruct;
Oid nodeOid;
XNodeInternal newTreeRoot = NULL;
XNodeInternal templRootInternal;
unsigned int resSizeEstimate = 0;
char *result = NULL;
XMLNodeOffset offRoot = 0;
XPathExprState exprState = NULL;
/* Retrieve parameter names out of the template. */
template = (xnt) PG_GETARG_VARLENA_P(0);
templData = VARDATA(template);
templDocRoot = (XMLCompNodeHdr) XNODE_ROOT(template);
Assert(templDocRoot->common.kind == XNTNODE_ROOT);
templHdrData = XNODE_ELEMENT_NAME(templDocRoot);
/* XML declaration: currently just the structure size, no padding. */
if (templDocRoot->common.flags & XNODE_DOC_XMLDECL)
{
templHdrData += sizeof(XMLDeclData);
}
templHdrData = (char *) TYPEALIGN(XNODE_ALIGNOF_XNT_HDR, templHdrData);
templHdr = (XNTHeader) templHdrData;
if (templHdr->paramCount > 0 || templHdr->substNodesCount > 0)
{
templHdrData += sizeof(XNTHeaderData);
}
if (templHdr->paramCount > 0)
{
unsigned short i;
templParNames = (char **) palloc(templHdr->paramCount * sizeof(char *));
for (i = 0; i < templHdr->paramCount; i++)
{
templParNames[i] = templHdrData;
templHdrData += strlen(templHdrData) + 1;
}
}
substNodeCount = templHdr->substNodesCount;
if (substNodeCount > 0)
{
templHdrData = (char *) TYPEALIGN(XNODE_ALIGNOF_NODE_OFFSET, templHdrData);
substNodes = (XMLNodeOffset *) templHdrData;
}
/* Retrieve names of the attributes that the function caller provides. */
parNameArr = PG_GETARG_ARRAYTYPE_P(1);
if (ARR_NDIM(parNameArr) == 0)
{
parNameCount = 0;
}
else if (ARR_NDIM(parNameArr) == 1)
{
int *attrArrDims = ARR_DIMS(parNameArr);
parNameCount = attrArrDims[0];
}
else
{
elog(ERROR, "parameter names must be passed in 1-dimensional array");
}
if (parNameCount != templHdr->paramCount)
{
elog(ERROR, "number of parameter names passed must be equal to the number of parameters that the template references");
}
if (parNameCount > 0)
{
Oid elType,
arrType;
int16 arrTypLen,
elTypLen;
bool elByVal;
char elAlign;
XNTParamNameSorted *parNamesTmp;
elType = parNameArr->elemtype;
arrType = get_array_type(elType);
Assert(arrType != InvalidOid);
arrTypLen = get_typlen(arrType);
get_typlenbyvalalign(elType, &elTypLen, &elByVal, &elAlign);
/*
* Get the parameter names from the array. There's no need to validate
* characters. The validation is performed when parsing the template,
* so invalid parameter names simply won't match.
*/
parNames = parNamesTmp = (XNTParamNameSorted *) palloc(parNameCount * sizeof(XNTParamNameSorted));
for (i = 0; i < parNameCount; i++)
{
int subscr[1];
Datum elDatum;
bool elIsNull;
char *parName;
subscr[0] = i + 1;
elDatum = array_ref(parNameArr, 1, subscr, arrTypLen, elTypLen, elByVal, elAlign, &elIsNull);
if (elIsNull)
{
elog(ERROR, "all parameter names must be not-NULL");
}
parName = TextDatumGetCString(elDatum);
if (strlen(parName) == 0)
{
elog(ERROR, "all parameter names must have non-zero length");
}
parNamesTmp->order = i;
parNamesTmp->name = parName;
parNamesTmp++;
}
if (parNameCount > 0)
{
char *prev;
if (parNameCount > 1)
{
/* Sort by the name. */
qsort(parNames, parNameCount, sizeof(XNTParamNameSorted), paramNameComparator);
/* Check if there's no duplicate. */
parNamesTmp = parNames;
prev = parNamesTmp->name;
parNamesTmp++;
for (i = 1; i < parNameCount; i++)
{
if (strcmp(prev, parNamesTmp->name) == 0)
{
elog(ERROR, "parameter '%s' is passed more than once", prev);
}
prev = parNamesTmp->name;
parNamesTmp++;
}
}
Assert(parNameCount == templHdr->paramCount);
paramMap = (unsigned short *) palloc(templHdr->paramCount * sizeof(unsigned short));
/* Match the template parameters to those passed to the function. */
for (i = 0; i < templHdr->paramCount; i++)
{
XNTParamNameSorted key;
XNTParamNameSorted *matching;
key.name = templParNames[i];
matching = (XNTParamNameSorted *) bsearch(&key, parNames, parNameCount,
sizeof(XNTParamNameSorted), paramNameComparator);
if (matching != NULL)
{
/*
* 'i' is the order that template expressions use to
* reference parameter names (i. e. the order of given
* parameter in the list stored in template header).
*
* paramMap[i] says at which position the user passes the
* corresponding parameter in the function argument list.
*/
paramMap[i] = matching->order;
}
else
{
elog(ERROR, "the template references parameter '%s' but it's not passed", key.name);
}
}
}
}
/* Retrieve values of the parameters. */
row = PG_GETARG_DATUM(2);
tupHdr = DatumGetHeapTupleHeader(row);
tupType = HeapTupleHeaderGetTypeId(tupHdr);
tupTypmod = HeapTupleHeaderGetTypMod(tupHdr);
tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
if (tupDesc->natts != parNameCount)
{
elog(ERROR, "the number of parameter names must be equal to that of values");
}
/*
* As there are no fixed OIDs for our types, let's get it from catalog.
* 'node' is the return type.
*/
tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
Assert(HeapTupleIsValid(tup));
procStruct = (Form_pg_proc) GETSTRUCT(tup);
nodeOid = procStruct->prorettype;
ReleaseSysCache(tup);
tmpTup.t_len = HeapTupleHeaderGetDatumLength(tupHdr);
tmpTup.t_data = tupHdr;
tup = &tmpTup;
parValues = (XPathExprOperandValue) palloc(tupDesc->natts * sizeof(XPathExprOperandValueData));
/*
* More than the default size because it will be used for all expressions
* in the template. (We can't free the cache when going to process another
* expression because parameters have to be available for all.)
*/
exprState = createXPathVarCache(4 * XPATH_VAR_CACHE_DEF_SIZE);
for (i = 0; i < tupDesc->natts; i++)
{
Oid attTyp;
Datum attValue;
bool isnull = false;
XPathExprOperandValue parValue;
attTyp = tupDesc->attrs[i]->atttypid;
attValue = heap_getattr(tup, i + 1, tupDesc, &isnull);
if (isnull)
{
elog(ERROR, "NULL value passed to parameter %u", i + 1);
}
parValue = parValues + i;
/*
* Set default values. 'negative' is not set because
* substituteParameters() ignores it anyway, in order to preserver
* sign that the source expression might contain.
*/
parValue->isNull = false;
parValue->castToNumber = false;
/* 'nodeOid' is not a constant so can't be used with 'switch'. */
if (attTyp == nodeOid)
{
xmlnode node = PG_DETOAST_DATUM(attValue);
XMLNodeHdr root = XNODE_ROOT(node);
parValue->v.nodeSet.count = 1;
parValue->v.nodeSet.isDocument = false;
parValue->v.nodeSet.nodes.nodeId = getXPathOperandId(exprState, (void *) root, XPATH_VAR_NODE_SINGLE);
parValue->type = XPATH_VAL_NODESET;
}
else
{
switch (attTyp)
{
case BOOLOID:
parValue->v.boolean = DatumGetBool(castParameterValue(attValue, attTyp, BOOLOID));
parValue->type = XPATH_VAL_BOOLEAN;
break;
case INT2OID:
case INT4OID:
case INT8OID:
case NUMERICOID:
case FLOAT4OID:
case FLOAT8OID:
parValue->v.num = DatumGetFloat8(castParameterValue(attValue, attTyp, FLOAT8OID));
parValue->type = XPATH_VAL_NUMBER;
break;
case BPCHAROID:
case VARCHAROID:
case TEXTOID:
{
char *valStr;
valStr = TextDatumGetCString(castParameterValue(attValue, attTyp, TEXTOID));
/*
* Maybe too restrictive, but that's not a problem.
* Only the resulting document is restricted by XML
* specification, whereas the XNT parameters are not.
* Thus so this check may be more strict if simplicity
* demands it.
*/
if (strchr(valStr, XNODE_CHAR_LARROW) || strchr(valStr, XNODE_CHAR_RARROW) ||
strchr(valStr, XNODE_CHAR_AMPERSAND))
{
elog(ERROR, "the following characters are is not allowed in text parameter: '<', '>', '&'.");
}
parValue->v.stringId = getXPathOperandId(exprState, (void *) valStr, XPATH_VAR_STRING);
parValue->type = XPATH_VAL_STRING;
break;
}
default:
elog(ERROR, "value of parameter %u has unrecognized type", i + 1);
break;
}
}
}
ReleaseTupleDesc(tupDesc);
templNode = getTemplateFromDoc(templDocRoot);
newTreeRoot = (XNodeInternal) palloc(sizeof(XNodeInternalData));
newTreeRoot->node = (XMLNodeHdr) templNode;
newTreeRoot->copy = false;
xmlnodeContainerInit(&newTreeRoot->children);
buildNewNodeTree((XMLNodeHdr) templNode, newTreeRoot, &resSizeEstimate,
parValues, paramMap, exprState, false);
/* Template node is the only child. */
Assert(newTreeRoot->children.position == 1);
templRootInternal = (XNodeInternal) newTreeRoot->children.content->value.singlePtr;
/*
* Empty template is not valid at parse time but the resulting document
* might get empty yet. For example whitespace-only text nodes or comments
* don't find their way to the result.
*/
if (templRootInternal->children.position > 0)
{
unsigned int resultSize;
char *resData,
*resTmp;
XMLNodeOffset *offRootPtr;
/* varlena header + root node offset */
resSizeEstimate += VARHDRSZ + MAX_PADDING(XNODE_ALIGNOF_NODE_OFFSET) + sizeof(XMLNodeOffset);
if (templRootInternal->children.position > 1)
{
/* Document fragment node in addition. */
resSizeEstimate += (XNODE_ALIGNOF_COMPNODE - 1) + sizeof(XMLCompNodeHdrData) +
templNode->children * sizeof(XMLNodeOffset);
}
result = (char *) palloc(resSizeEstimate);
resData = resTmp = result + VARHDRSZ;
if (templRootInternal->children.position == 1)
{
writeXMLNodeInternal(templRootInternal->children.content->value.singlePtr, true, &resTmp, &offRoot);
}
else
{
/*
* The template will be written too in addition to its children
* and finally turned into a document fragment.
*/
writeXMLNodeInternal(templRootInternal, true, &resTmp, &offRoot);
}
resTmp = (char *) TYPEALIGN(XNODE_ALIGNOF_NODE_OFFSET, resTmp);
offRootPtr = (XMLNodeOffset *) resTmp;
*offRootPtr = offRoot;
resTmp += sizeof(XMLNodeOffset);
resultSize = (char *) resTmp - result;
SET_VARSIZE(result, resultSize);
}
freeTemplateTree(newTreeRoot);
freeExpressionState(exprState);
if (paramMap != NULL)
{
pfree(paramMap);
}
if (parNames != NULL)
{
unsigned short i;
for (i = 0; i < parNameCount; i++)
{
pfree(parNames[i].name);
}
pfree(parNames);
}
if (templParNames != NULL)
{
pfree(templParNames);
}
if (parValues != NULL)
{
pfree(parValues);
}
if (result != NULL)
{
PG_RETURN_POINTER(result);
}
else
{
PG_RETURN_NULL();
}
}
/*
* Caller must not pass combination of source and target types for which
* there's no entry in pg_cast.
*/
static Datum
castParameterValue(Datum value, Oid sourceType, Oid targetType)
{
HeapTuple tup;
Form_pg_cast castStruct;
Oid castFuncOid;
if (sourceType == targetType)
{
return value;
}
tup = SearchSysCache2(CASTSOURCETARGET, ObjectIdGetDatum(sourceType), ObjectIdGetDatum(targetType));
Assert(HeapTupleIsValid(tup));
castStruct = (Form_pg_cast) GETSTRUCT(tup);
castFuncOid = castStruct->castfunc;
ReleaseSysCache(tup);
if (castFuncOid == 0)
{
return value;
}
return OidFunctionCall1(castFuncOid, value);
}
/*
* Generates the template tree with parameters substituted.
* XNTNODE_TEMPLATE must be the root for the outermost call.
*
* 'storageSize' gets incremented by the storage size required for the current node plus that of children.
* For template only children and the corresponding references are accounted, while the template
* node itself is ignored.
*/
static void
buildNewNodeTree(XMLNodeHdr node, XNodeInternal parent, unsigned int *storageSize,
XPathExprOperandValue paramValues, unsigned short *paramMap, XPathExprState exprState,
bool preserveSpace)
{
XNodeInternal nodeInternal = NULL;
/* Process children if the node has some. */
if (node->kind == XMLNODE_ELEMENT || node->kind == XNTNODE_TEMPLATE)
{
XMLCompNodeHdr compNode = (XMLCompNodeHdr) node;
XMLNodeIteratorData iterator;
XMLNodeHdr childNode;
unsigned int children = 0;
nodeInternal = getInternalNode(node, false);
xmlnodeContainerInit(&nodeInternal->children);
initXMLNodeIterator(&iterator, compNode, true);
while ((childNode = getNextXMLNodeChild(&iterator)) != NULL)
{
children++;
if (node->kind == XNTNODE_TEMPLATE)
{
XNTAttrNames *attrInfo = xntAttributeInfo;
XMLNodeOffset childRef;
if (children <= attrInfo->number)
{
childRef = (char *) node - (char *) childNode;
if (childRef == XMLNodeOffsetInvalid)
/* Unused optional attribute. */
continue;
if ((children - 1) == XNT_TEMPLATE_PRESERVE)
{
char *valueStr = XNODE_CONTENT(childNode);
Datum boolDatum = DirectFunctionCall1Coll(boolin, InvalidOid, CStringGetDatum(valueStr));
preserveSpace = DatumGetBool(boolDatum);
continue;
}
}
else if (childNode->kind == XMLNODE_ATTRIBUTE)
/* Only namespace declaration should make this happen. */
continue;
}
if ((childNode->kind == XMLNODE_TEXT || childNode->kind == XMLNODE_CDATA) &&
!preserveSpace && whitespacesOnly(XNODE_CONTENT(childNode)))
{
continue;
}
if (childNode->kind == XMLNODE_COMMENT)
{
continue;
}
buildNewNodeTree(childNode, nodeInternal, storageSize, paramValues, paramMap, exprState, preserveSpace);
}
}
if (node->flags & XNODE_EL_SPECIAL)
{
/*
* The resulting node (meaning storage) will not contain the template
* node itself, so we don't have to process (construct) it, neither
* evaluate its size.
*/
if (node->kind != XNTNODE_TEMPLATE)
{
XMLCompNodeHdr xntNode;
char *refPtr;
char bwidth;
XMLNodeOffset offRel;
XMLNodeHdr attrNode;
XMLNodeHdr resultNode;
unsigned int resultNodeSize = 0;
switch (node->kind)
{
case XNTNODE_COPY_OF:
{
XPathExpression expr,
exprCopy;
XPathExprOperandValueData exprResult;
/* Expression must be the first attribute. */
xntNode = (XMLCompNodeHdr) node;
refPtr = XNODE_FIRST_REF(xntNode);
bwidth = XNODE_GET_REF_BWIDTH(xntNode);
offRel = readXMLNodeOffset(&refPtr, bwidth, false);
attrNode = (XMLNodeHdr) ((char *) xntNode - offRel);
Assert(attrNode->kind == XMLNODE_ATTRIBUTE);
Assert(attrNode->flags & XNODE_ATTR_VALUE_BINARY);
/*
* Special attributes only contain the value. (Name is
* determined by the position.)
*/
expr = (XPathExpression) XNODE_CONTENT(attrNode);
exprCopy = substituteParameters(exprState, expr, paramValues, paramMap);
evaluateXPathExpression(exprState, exprCopy, 0, &exprResult);
if (!exprResult.isNull)
{
if (exprResult.type == XPATH_VAL_NODESET)
{
XPathNodeSet ns = &exprResult.v.nodeSet;
XMLNodeHdr *nodes;
unsigned short j;
Assert(ns->count > 0);
if (ns->count == 1)
{
XMLNodeHdr single = getXPathOperandValue(exprState, ns->nodes.nodeId, XPATH_VAR_NODE_SINGLE);
nodes = &single;
}
else
{
nodes = (XMLNodeHdr *) getXPathOperandValue(exprState, ns->nodes.arrayId, XPATH_VAR_NODE_ARRAY);
}
for (j = 0; j < ns->count; j++)
{
XMLNodeHdr singleNode = nodes[j];
/*
* Fragment must have been turned into
* node-set during parameter substitution.
*/
Assert(singleNode->kind != XMLNODE_DOC_FRAGMENT);
if ((singleNode->kind == XMLNODE_TEXT && whitespacesOnly(XNODE_CONTENT(singleNode))) ||
singleNode->kind == XMLNODE_COMMENT)
{
continue;
}
/*
* No need to initialize 'nodeInternal'.
* We're only interested in the child
* node.
*/
buildNewNodeTree(singleNode, parent, storageSize, paramValues, paramMap, exprState,
preserveSpace);
}
}
else
{
XPathExprOperandValueData resStr;
char *strValue,
*content;
castXPathExprOperandToStr(exprState, &exprResult, &resStr);
strValue = getXPathOperandValue(exprState, resStr.v.stringId, XPATH_VAR_STRING);
resultNodeSize = sizeof(XMLNodeHdr) + strlen(strValue) +1;
resultNode = (XMLNodeHdr) palloc(resultNodeSize);
resultNode->kind = XMLNODE_TEXT;
/*
* TODO 1. Ensure there are no invalid chars
* 2. Some flags might need to be set,
* especially XNODE_TEXT_SPEC_CHARS
*/
resultNode->flags = 0;
content = XNODE_CONTENT(resultNode);
strcpy(content, strValue);
nodeInternal = getInternalNode(resultNode, true);
*storageSize += resultNodeSize + sizeof(XMLNodeOffset);
}
}
pfree(exprCopy);
}
break;
case XNTNODE_ELEMENT:
{
XMLNodeHdr childNode;
char *elName;
bool elNameCopy = false;
unsigned short i,
elNodeAttrs = 0;
/* Element name is the first attribute. */
xntNode = (XMLCompNodeHdr) node;
refPtr = XNODE_FIRST_REF(xntNode);
bwidth = XNODE_GET_REF_BWIDTH(xntNode);
offRel = readXMLNodeOffset(&refPtr, bwidth, true);
attrNode = (XMLNodeHdr) ((char *) xntNode - offRel);
Assert(attrNode->kind == XMLNODE_ATTRIBUTE);
elName = XNODE_CONTENT(attrNode);
if (attrNode->flags & XNODE_ATTR_VALUE_BINARY)
{
elName = dumpBinaryAttrValue(elName, NULL, paramValues, paramMap, exprState);
elNameCopy = true;
}
if (!isValidXMLName(elName))
{
elog(ERROR, "'%s' is not a valid element name", elName);
}
resultNodeSize = sizeof(XMLCompNodeHdrData) + strlen(elName) +1;
resultNode = (XMLNodeHdr) palloc(resultNodeSize);
resultNode->kind = XMLNODE_ELEMENT;
/*
* This won't be effective anyway. The flag values
* will be determined when the template is going to be
* written to storage.
*/
resultNode->flags = 0;
/*
* 'resultNode' is just temporary. As such it does not
* have to store references. 'nodeInternal->children'
* will hold them instead, until the 'real' storage is
* created. by writeXMLNodeInternal().
*
* 'children' is set to 0 regardless the actual number
* of children. The consequence is that
* XNODE_ELEMENT_NAME() does not leave any space for
* references when creating the temporary element, and
* in turn XNODE_ELEMENT_NAME() can be consistent in
* reading the name when inserting the node into the
* storage.
*/
((XMLCompNodeHdr) resultNode)->children = 0;
strcpy(XNODE_ELEMENT_NAME((XMLCompNodeHdr) resultNode), elName);
if (elNameCopy)
{
pfree(elName);
}
nodeInternal = getInternalNode(resultNode, true);
xmlnodeContainerInit(&nodeInternal->children);
/*
* TODO Possible optional attributes (which should
* only be namespace declarations) are currently
* skipped. Such namespaces may need to be applied to
* the the nested nodes.
*/
for (i = 1; i < xntNode->children; i++)
{
/*
* 'step=false' so that we stay at the node if
* it's the first non-attribute.
*/
offRel = readXMLNodeOffset(&refPtr, bwidth, false);
childNode = (XMLNodeHdr) ((char *) xntNode - offRel);
if (childNode->kind != XMLNODE_ATTRIBUTE)
{
break;
}
refPtr += bwidth;
}
/*
* Add non-attribute children to the new node. This
* refers to non-attribute child nodes of the
* 'xnt:element' node, which may include
* 'xnt:attribute' tags (i.e. attrbutes of the node
* being constructed)
*/
for (; i < xntNode->children; i++)
{
offRel = readXMLNodeOffset(&refPtr, bwidth, true);
childNode = (XMLNodeHdr) ((char *) xntNode - offRel);
if ((childNode->kind == XMLNODE_TEXT && whitespacesOnly(XNODE_CONTENT(childNode))) ||
childNode->kind == XMLNODE_COMMENT)
{
continue;
}
buildNewNodeTree(childNode, nodeInternal, storageSize, paramValues, paramMap, exprState,
preserveSpace);
}
if (nodeInternal->children.position == elNodeAttrs)
{
resultNode->flags |= XNODE_EMPTY;
}
*storageSize += MAX_PADDING(XNODE_ALIGNOF_COMPNODE) +
resultNodeSize + sizeof(XMLNodeOffset);
}
break;
case XNTNODE_ATTRIBUTE:
{
char *attrName,
*attrValue;
bool attrNameCopy = false;
bool attrValueCopy = false;
char *cnt;
xntNode = (XMLCompNodeHdr) node;
refPtr = XNODE_FIRST_REF(xntNode);
bwidth = XNODE_GET_REF_BWIDTH(xntNode);
offRel = readXMLNodeOffset(&refPtr, bwidth, true);
attrNode = (XMLNodeHdr) ((char *) xntNode - offRel);
attrName = XNODE_CONTENT(attrNode);
if (attrNode->flags & XNODE_ATTR_VALUE_BINARY)
{
attrName = dumpBinaryAttrValue(attrName, NULL, paramValues, paramMap, exprState);
attrNameCopy = true;
}
if (!isValidXMLName(attrName))
{
elog(ERROR, "'%s' is not a valid attribute name", attrName);
}
offRel = readXMLNodeOffset(&refPtr, bwidth, true);
attrNode = (XMLNodeHdr) ((char *) xntNode - offRel);
attrValue = XNODE_CONTENT(attrNode);
if (attrNode->flags & XNODE_ATTR_VALUE_BINARY)
{
attrValue = dumpBinaryAttrValue(attrValue, NULL, paramValues, paramMap, exprState);
resultNodeSize = 0;
resultNode = getNewAttribute(attrName, 0, attrValue, true, &resultNodeSize);
attrValueCopy = true;
}
else
{
/*
* No validation of the value required in this
* case: parser must have done it when parsing the
* template.
*/
resultNodeSize = sizeof(XMLNodeHdrData) + strlen(attrName) +strlen(attrValue) + 2;
resultNode = (XMLNodeHdr) palloc(resultNodeSize);
resultNode->kind = XMLNODE_ATTRIBUTE;
resultNode->flags = attrNode->flags;
cnt = XNODE_CONTENT(resultNode);
strcpy(cnt, attrName);
cnt += strlen(attrName) + 1;
strcpy(cnt, attrValue);
}
if (attrNameCopy)
{
pfree(attrName);
}
if (attrValueCopy)
{
pfree(attrValue);
}
nodeInternal = getInternalNode(resultNode, true);
*storageSize += resultNodeSize + sizeof(XMLNodeOffset);
}
break;
default:
elog(ERROR, "unrecognized special node %u", node->kind);
break;
}
}
}
else
{ /* Not a special) element. */
if (node->kind == XMLNODE_ELEMENT)
{
char *name = XNODE_ELEMENT_NAME((XMLCompNodeHdr) node);
/*
* getXMLNodeSize() is not used here because child references
* might need more space in the new tree. When constructing the
* node from template we treat the references separate, see above.
*/
*storageSize += MAX_PADDING(XNODE_ALIGNOF_COMPNODE) +
sizeof(XMLCompNodeHdrData) + strlen(name) +1;
}
else if (node->kind == XMLNODE_ATTRIBUTE && (node->flags & XNODE_ATTR_VALUE_BINARY))
{
XMLNodeHdr attrOrig,
attrNew;
char *name,
*valueOrig,
*valueNew;
unsigned int sizeNew = 0;
/* Construct attribute as 2 NULL-terminated strings (name, value). */
attrOrig = node;
name = XNODE_CONTENT(attrOrig);
valueOrig = name + strlen(name) + 1;
valueNew = dumpBinaryAttrValue(valueOrig, NULL, paramValues, paramMap, exprState);
attrNew = getNewAttribute(name, attrOrig->flags, valueNew, false, &sizeNew);
nodeInternal = getInternalNode(attrNew, true);
pfree(valueNew);
*storageSize += sizeNew;
}
else
{
nodeInternal = getInternalNode(node, false);
*storageSize += getXMLNodeSize(node, false);
}
*storageSize += sizeof(XMLNodeOffset);
}
if (nodeInternal != NULL)
{
xmlnodePushSinglePtr(&parent->children, nodeInternal);
}
}
/*
* Returns a new attribute node where the value has been validated and flags set as appropriate.
*/
static XMLNodeHdr
getNewAttribute(char *name, uint8 flags, char *value, char decideOnDelim, unsigned int *size)
{
XMLNodeParserStateData state;
char *valueValidated;
bool refs = false;
XMLNodeHdr result;
char *c;
char delimiter;
bool seenApostrophe = false;
bool mixture = false;
if (decideOnDelim)
{
delimiter = XNODE_CHAR_QUOTMARK;
}
else
{
delimiter = ((flags & XNODE_ATTR_APOSTROPHE) == 0) ? XNODE_CHAR_QUOTMARK : XNODE_CHAR_APOSTR;
}
initXMLParserState(&state, value, XMLNODE_ATTRIBUTE, NULL);
/*
* If the value contains character references then the binary value will
* be different. That's why use 'valueNewChecked' in the next steps.
*/
valueValidated = readXMLAttValue(&state, true, &refs);
c = valueValidated;
while (*c != '\0')
{
if (decideOnDelim)
{
/*
* Check for mixture of apostrophes and quotation marks is not
* necessary, readXMLName() shouldn't accept it. However it's not
* a significant overhead to cross-check for such mixtures while
* determining the delimiter.
*/
if (*c == XNODE_CHAR_APOSTR)
{
if (delimiter == XNODE_CHAR_APOSTR)
{
/*
* We already switched the delimiter from quotation mark
* to apostrophe, so the quot. mark is there too.
*/
mixture = true;
break;
}
seenApostrophe = true;
}
else if (*c == XNODE_CHAR_QUOTMARK)
{
/* Try to change the delimiter to apostrophe. */
if (!seenApostrophe)
{
delimiter = XNODE_CHAR_APOSTR;
}
else
{
mixture = true;
break;
}
}
}
else
{
/*
* If apostrophe is used as delimiter in the template, then it
* must not be inserted via parameter. The same is valid for
* quotation mark.
*/
if ((*c == XNODE_CHAR_QUOTMARK && ((flags & XNODE_ATTR_APOSTROPHE) == 0)) ||
(*c == XNODE_CHAR_APOSTR && (flags & XNODE_ATTR_APOSTROPHE)))
{
elog(ERROR, "attribute value delimiter must not be used within the value");
}
}
c += pg_utf_mblen((unsigned char *) c);
}
if (mixture)
{
elog(ERROR, "attribute value must not contain both quotation mark and apostrophe.");
}
*size = sizeof(XMLNodeHdr) + strlen(name) +strlen(valueValidated) + 2;
result = (XMLNodeHdr) palloc(*size);
result->kind = XMLNODE_ATTRIBUTE;
c = XNODE_CONTENT(result);
strcpy(c, name);
c += strlen(name) + 1;
strcpy(c, valueValidated);
result->flags = getXMLAttributeFlags(valueValidated, refs, delimiter == XNODE_CHAR_APOSTR);
finalizeXMLParserState(&state);
return result;
}
static XNodeInternal
getInternalNode(XMLNodeHdr node, bool copy)
{
XNodeInternal result = (XNodeInternal) palloc(sizeof(XNodeInternalData));
result->node = node;
result->copy = copy;
return result;
}
static void
freeTemplateTree(XNodeInternal root)
{
if (root->node->kind == XMLNODE_ELEMENT || root->node->kind == XNTNODE_TEMPLATE)
{
unsigned short i;
XMLNodeContainer children;
XNodeListItem *child;
children = &root->children;
child = children->content;
for (i = 0; i < children->position; i++)
{
freeTemplateTree(child->value.singlePtr);
child++;
}
xmlnodeContainerFree(&root->children);
}
if (root->copy)
{
pfree(root->node);
}
pfree(root);
}
/*
* The template document might contain multiple root nodes, even though there's
* exactly one root element). This function returns that root element, i.e. 'xnt:template'
*/
static XMLCompNodeHdr
getTemplateFromDoc(XMLCompNodeHdr docRoot)
{
XMLNodeIteratorData iterator;
XMLNodeHdr child;
Assert(docRoot->common.kind == XNTNODE_ROOT);
initXMLNodeIterator(&iterator, docRoot, true);
while ((child = getNextXMLNodeChild(&iterator)) != NULL)
{
if (child->kind == XNTNODE_TEMPLATE)
{
break;
}
}
Assert(child != NULL && child->kind == XNTNODE_TEMPLATE);
return (XMLCompNodeHdr) child;
}
static XPathExpression
substituteParameters(XPathExprState exprState, XPathExpression expression, XPathExprOperandValue paramValues,
unsigned short *paramMap)
{
unsigned short i;
XPathExpression result = (XPathExpression) palloc(expression->common.size);
XPathOffset *varOffPtr = (XPathOffset *) ((char *) result + sizeof(XPathExpressionData));
memcpy(result, expression, expression->common.size);
for (i = 0; i < result->variables; i++)
{
XPathExprOperand opnd = (XPathExprOperand) ((char *) result + *varOffPtr);
if (opnd->common.type == XPATH_OPERAND_PARAMETER)
{
unsigned short valueId;
XPathExprOperandValue value;
XMLCompNodeHdr fragment = NULL;
XPathExprGenericValue *valDst;
valueId = paramMap[opnd->value.v.paramId];
value = paramValues + valueId;
/*
* 'negative' is not set, that of the source expression must be
* preserved.
*/
opnd->value.castToNumber = value->castToNumber;
opnd->value.isNull = value->isNull;
opnd->value.type = value->type;
/*
* Document fragment has to be turned into node set.
*
* This only makes sense if the operand contains exactly one node.
* There's no known case where array of document fragments could
* be constructed.
*/
if (!value->isNull && value->type == XPATH_VAL_NODESET && value->v.nodeSet.count == 1)
{
XMLNodeHdr node = getXPathOperandValue(exprState, value->v.nodeSet.nodes.nodeId, XPATH_VAR_NODE_SINGLE);
if (node->kind == XMLNODE_DOC_FRAGMENT)
{
fragment = (XMLCompNodeHdr) node;
}
}
valDst = &opnd->value.v;
if (fragment != NULL)
{
XPathNodeSet ns = &valDst->nodeSet;
unsigned short j = 0;
XMLNodeHdr *children;
XMLNodeIteratorData iterator;
XMLNodeHdr child;
Assert(fragment->children > 1);
children = (XMLNodeHdr *) palloc(fragment->children * sizeof(XMLNodeHdr));
ns->isDocument = false;
ns->count = fragment->children;
initXMLNodeIterator(&iterator, fragment, true);
while ((child = getNextXMLNodeChild(&iterator)) != NULL)
{
children[j++] = child;
}
ns->nodes.arrayId = getXPathOperandId(exprState, (void *) children, XPATH_VAR_NODE_ARRAY);
}
else
{
XPathExprGenericValue *valSrc;
valSrc = &value->v;
memcpy(valDst, valSrc, sizeof(XPathExprGenericValue));
}
}
varOffPtr++;
}
return result;
}
static bool
whitespacesOnly(char *str)
{
while (*str != '\0')
{
unsigned int cWidth = pg_utf_mblen((unsigned char *) str);
if (!XNODE_WHITESPACE(str))
{
break;
}
str += cWidth;
}
/* The string contains merely white spaces. */
return (*str == '\0');
}