eudora-mac/xml.c

1 line
23 KiB
C
Executable File

/* Copyright (c) 2017, Computer History Museum
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
the limitations in the disclaimer below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE. */
#include "xml.h"
#define FILE_NUM 136
// prototypes
static void UnGetTokenChar(TokenInfo *pInfo);
static unsigned char PeekTokenChar(TokenInfo *pInfo,short offset);
static long CopyWithCharConv(UPtr from, UPtr to, long len);
OSErr XMLParseElement(TokenInfo *tip, XMLBinPtr xmlbP);
// Global variables
short gIndent;
/************************************************************************
* Generate XML
************************************************************************/
/************************************************************************
* AccuAddXMLObject - add an XML value from a string
************************************************************************/
void AccuAddXMLObject(AccuPtr a,StringPtr sKeyword,StringPtr value)
{
AccuIndent(a);
AccuAddTag(a,sKeyword,false);
AccuAddStr(a,value);
AccuAddTag(a,sKeyword,true);
AccuAddCRLF(a);
}
/************************************************************************
* AccuAddXMLObjectInt - add an XML value from an integer
************************************************************************/
void AccuAddXMLObjectInt(AccuPtr a,StringPtr sKeyword,long value)
{
Str32 s;
NumToString(value,s);
AccuIndent(a);
AccuAddTag(a,sKeyword,false);
AccuAddStr(a,s);
AccuAddTag(a,sKeyword,true);
AccuAddCRLF(a);
}
/************************************************************************
* AccuAddTagLine - add a tag on a line by itself
************************************************************************/
void AccuAddTagLine(AccuPtr a,StringPtr sKeyword,Boolean endTag)
{
AccuIndent(a);
AccuAddTag(a,sKeyword,endTag);
AccuAddCRLF(a);
}
/************************************************************************
* AccuAddXMLObjectHandle - add an XML value from a handle
************************************************************************/
void AccuAddXMLObjectHandle(AccuPtr a,StringPtr sKeyword,Handle hValue)
{
AccuIndent(a);
AccuAddTag(a,sKeyword,false);
AccuAddHandle(a,hValue);
AccuAddTag(a,sKeyword,true);
AccuAddCRLF(a);
}
/************************************************************************
* AccuAddXMLWithAttrPtr - add an XML object with attributes from a ptr
************************************************************************/
void AccuAddXMLWithAttrPtr(AccuPtr a,StringPtr sKeyword,Ptr pAttr,long len,Boolean emptyElement)
{
AccuIndent(a);
AccuAddChar(a,'<');
AccuAddStr(a,sKeyword);
AccuAddChar(a,' ');
AccuAddPtr(a,pAttr,len);
if (emptyElement)
AccuAddChar(a,'/');
AccuAddChar(a,'>');
AccuAddCRLF(a);
}
/************************************************************************
* AccuAddXMLWithAttr - add an XML object with attributes from a string
************************************************************************/
void AccuAddXMLWithAttr(AccuPtr a,StringPtr sKeyword,StringPtr sAttr,Boolean emptyElement)
{
AccuAddXMLWithAttrPtr(a,sKeyword,sAttr+1,*sAttr,emptyElement);
}
/************************************************************************
* AccuAddTag - add a keyword
************************************************************************/
void AccuAddTag(AccuPtr a,StringPtr sKeyword,Boolean endTag)
{
AccuAddChar(a,'<');
if (endTag) AccuAddChar(a,'/');
AccuAddStr(a,sKeyword);
AccuAddChar(a,'>');
}
/************************************************************************
* AccuAddCRLF - add CR and LF
************************************************************************/
void AccuAddCRLF(AccuPtr a)
{
AccuAddChar(a,'\r');
AccuAddChar(a,'\n');
}
/************************************************************************
* AccuIndent - add tabs to indent
************************************************************************/
void AccuIndent(AccuPtr a)
{
short n;
for(n=gIndent;n--;)
AccuAddChar(a,tabChar);
}
/************************************************************************
* XMLNoIndent - reset indentation
************************************************************************/
void XMLNoIndent(void)
{
gIndent = 0;
}
/************************************************************************
* XMLIncIndent - increment indentation
************************************************************************/
void XMLIncIndent(void)
{
gIndent++;
}
/************************************************************************
* XMLDecIndent - decrement indentation
************************************************************************/
void XMLDecIndent(void)
{
if (gIndent) gIndent--;
}
/************************************************************************
* Parse XML
************************************************************************/
/************************************************************************
* TokenToURL - put token in handle as c-string
************************************************************************/
OSErr TokenToURL(long *offset,AccuPtr a,TokenInfo *tokenInfo)
{
Handle hURL;
OSErr err = noErr;
if (offset) *offset = a->offset;
if (hURL = NuHTempOK(tokenInfo->tokenLen+1))
{
long len;
// Convert any character references
len = CopyWithCharConv(tokenInfo->pText+tokenInfo->tokenStart, LDRef(hURL), tokenInfo->tokenLen);
UL(hURL);
if (len != tokenInfo->tokenLen)
SetHandleSize(hURL,len+1);
// Add c-string null terminator
(*hURL)[len] = 0;
err = AccuAddHandle(a,hURL);
ZapHandle(hURL);
}
else err = MemError();
return err;
}
/************************************************************************
* GetNextTokenChar - get the next character
************************************************************************/
unsigned char GetNextTokenChar(TokenInfo *pInfo)
{
if (pInfo->offset < pInfo->size)
return (pInfo->pText)[pInfo->offset++];
else
return 0;
}
/************************************************************************
* CopyWithCharConv - copy characters, processing character references
************************************************************************/
static long CopyWithCharConv(UPtr from, UPtr to, long len)
{
char c;
enum { kLiteral,kDecimal,kHex } refType;
Str255 s;
long num;
TokenInfo textInfo;
long toLen = 0;
textInfo.pText = from;
textInfo.size = len;
textInfo.offset = 0;
while (c = GetNextTokenChar(&textInfo))
{
if (c=='&')
{
refType = kLiteral;
c = GetNextTokenChar(&textInfo);
if (c=='#')
{
// Numeric encoding
c = GetNextTokenChar(&textInfo);
if (c=='x' || c=='X')
{
// Hexadecimal
c = GetNextTokenChar(&textInfo);
refType = kHex;
}
else
refType = kDecimal;
}
// Get reference
for (*s = 0; c && c!=';'; c = GetNextTokenChar(&textInfo))
PCatC(s,c);
switch (refType)
{
case kLiteral:
MyLowerStr(s);
c = FindSTRNIndex(HTMLLiteralsStrn,s);
break;
case kDecimal:
StringToNum(s,&num);
c = num;
break;
case kHex:
if (*s<2)
{
s[2]=s[1];
s[1]='0';
s[0] = 2;
}
Hex2Bytes(s+*s-1,2,&c);
break;
}
}
*to++ = c;
toLen++;
}
return toLen;
}
/************************************************************************
* TokenToString - copy a token into a string
************************************************************************/
PStr TokenToString(TokenInfo *pInfo, StringPtr s)
{
if (pInfo->tokenLen < 256)
*s = CopyWithCharConv(pInfo->pText+pInfo->tokenStart, s+1, pInfo->tokenLen);
else
*s = 0;
return s;
}
/************************************************************************
* PeekTokenChar - peek at a character
************************************************************************/
static unsigned char PeekTokenChar(TokenInfo *pInfo,short offset)
{
if (pInfo->offset+offset < pInfo->size)
return (pInfo->pText)[pInfo->offset+offset];
else
return 0;
}
/************************************************************************
* UnGetTokenChar - backup a character
************************************************************************/
static void UnGetTokenChar(TokenInfo *pInfo)
{
if (pInfo->offset)
pInfo->offset--;
}
/************************************************************************
* IsWhiteSpace - is character white space (including PC LF's)
************************************************************************/
Boolean IsWhiteSpace(char c)
{
return c==' ' || c==0x9 || c==0xd || c==0xa;
}
/************************************************************************
* SkipWhiteSpace - get the next character that's not white space
************************************************************************/
unsigned char SkipWhiteSpace(TokenInfo *pInfo)
{
unsigned char c;
do
{
c = GetNextTokenChar(pInfo);
} while (IsWhiteSpace(c));
return c;
}
/************************************************************************
* GetNextToken - return the next token (element or element content), don't return comments
************************************************************************/
short GetNextToken(TokenInfo *pInfo)
{
unsigned char c,cLast;
short tokenType;
do
{
c = SkipWhiteSpace(pInfo);
if (c=='<')
{
// Tag
tokenType = kElementTag;
c = SkipWhiteSpace(pInfo);
if (c=='!' && PeekTokenChar(pInfo,0)=='-' && PeekTokenChar(pInfo,1)=='-')
{
// Comment
pInfo->offset += 2;
do
{
c = GetNextTokenChar(pInfo);
} while (c && (c!='-' || PeekTokenChar(pInfo,0)!='-' || PeekTokenChar(pInfo,1)!='>'));
pInfo->offset += 2;
tokenType = kCommentTag;
continue;
}
if (c=='?')
{
// Processing instruction, skip it
do
{
c = GetNextTokenChar(pInfo);
} while (c && c!='>');
tokenType = kCommentTag;
continue;
}
if (c=='/')
{
c = GetNextTokenChar(pInfo);
tokenType = kEndTag;
}
// Find end of tag
pInfo->attrStart = pInfo->attrLen = 0;
pInfo->tokenStart = pInfo->offset-1;
do
{
cLast = c;
c = GetNextTokenChar(pInfo);
} while (c && c!='>' && !IsWhiteSpace(c));
pInfo->tokenLen = pInfo->offset-1-pInfo->tokenStart;
if (cLast=='/')
{
// Empty element tag
tokenType = kEmptyElementTag;
pInfo->tokenLen--;
}
if (IsWhiteSpace(c))
{
// Token has attributes
c = SkipWhiteSpace(pInfo);
pInfo->attrStart = pInfo->offset-1;
while (c && c!='>')
{
cLast = c;
c = GetNextTokenChar(pInfo);
};
pInfo->attrLen = pInfo->offset-1-pInfo->attrStart;
if (cLast=='/')
{
// Empty element tag
tokenType = kEmptyElementTag;
pInfo->attrLen--;
}
}
}
else if (c)
{
// Not tag. Should be element content
pInfo->tokenStart = pInfo->offset-1;
do
{
c = GetNextTokenChar(pInfo);
} while (c && c != '<');
UnGetTokenChar(pInfo);
pInfo->tokenLen = pInfo->offset - pInfo->tokenStart;
tokenType = kContent;
}
else
// End of file
tokenType = kTokenDone;
} while (tokenType == kCommentTag);
return tokenType;
}
/************************************************************************
* GetTokenIdx - find token in string list or add to list
************************************************************************/
short GetTokenIdx(AccuPtr accuKeywordList,StringPtr sToken)
{
short result;
if (!(result = FindSTRNIndexRes(accuKeywordList->data,sToken)))
{
// Not in token list. Add it.
if (!AccuAddPtr(accuKeywordList,sToken,*sToken+1))
result = ++*(short *)*accuKeywordList->data;
else
result = 0;
}
return result;
}
/************************************************************************
* GetAttribute - get next attribute
************************************************************************/
short GetAttribute(TokenInfo *pInfo,StringPtr sValue)
{
short result = 0;
if (pInfo->attrLen)
{
long saveOffset = pInfo->offset;
long saveSize = pInfo->size;
unsigned char c;
// Redirect token info to look at attributes
pInfo->offset = pInfo->attrStart;
pInfo->size = pInfo->attrStart+pInfo->attrLen;
// Get attribute
if (c = SkipWhiteSpace(pInfo))
{
pInfo->tokenStart = pInfo->offset-1;
do
{
c = GetNextTokenChar(pInfo);
} while (c && c!='=' && !IsWhiteSpace(c));
if (c=='=')
{
pInfo->tokenLen = pInfo->offset-1-pInfo->tokenStart;
TokenToString(pInfo, sValue);
// Get attribute value
if (SkipWhiteSpace(pInfo)=='"')
{
result = GetTokenIdx(pInfo->aKeywords,sValue)-1;
pInfo->tokenStart = pInfo->offset;
do
{
c = GetNextTokenChar(pInfo);
} while (c && c!='"');
pInfo->tokenLen = pInfo->offset-1-pInfo->tokenStart;
TokenToString(pInfo, sValue);
}
}
}
// Advance to next attribute
pInfo->attrLen -= pInfo->offset-pInfo->attrStart;
pInfo->attrStart = pInfo->offset;
// Restore info
pInfo->offset = saveOffset;
pInfo->size = saveSize;
}
return result;
}
/************************************************************************
* GetNumAttribute - get next numeric attribute
************************************************************************/
short GetNumAttribute(TokenInfo *pInfo,long *value)
{
short idx;
Str255 sValue;
if (idx = GetAttribute(pInfo,sValue))
StringToNum(sValue,value);
return idx;
}
/************************************************************************
* XMLParse - parse an xml stream into a binary representation that can
* be easily traversed
************************************************************************/
OSErr XMLParse(UHandle text,short keyWordStrn,AccuPtr keywords,StackHandle xmlbStack)
{
TokenInfo tokenInfo;
short i;
Str63 keyword;
XMLBinary xmlb;
OSErr err;
Accumulator a;
// setup the context
Zero(tokenInfo);
Zero(a);
tokenInfo.pText = LDRef(text);
tokenInfo.size = GetHandleSize(text);
tokenInfo.offset = 0;
tokenInfo.aKeywords = keywords ? keywords : &a;
if (keyWordStrn)
{
AccuAddChar(&a,0);
AccuAddChar(&a,0);
for (i=1;*GetRString(keyword,keyWordStrn+i);i++) AccuAddPtr(tokenInfo.aKeywords,keyword,*keyword+1);
if (a.offset > 2)
*(short*)*a.data = i-1;
}
// read elements til we're done
do
{
err = XMLParseElement(&tokenInfo,&xmlb);
if (!err) err = StackPush(&xmlb,xmlbStack);
}
while (!err);
if (err==-1) err = noErr; // that's how the world ends
// cleanup
if (keywords) AccuTrim(keywords);
AccuZap(a);
UL(text);
// show me the way to go home...
return err;
}
/************************************************************************
* XMLParseElement - parse the next element in an xml context; recurse
* if necessary
************************************************************************/
OSErr XMLParseElement(TokenInfo *tip, XMLBinPtr xmlbP)
{
OSErr err = noErr;
// start with a clean slate
Zero(*xmlbP);
// read the next token
xmlbP->self.type = GetNextToken(tip);
// are we done?
if (xmlbP->self.type==kTokenDone) return -1;
// guess not. Analyze value.
TokenToString(tip,GlobalTemp);
if (*GlobalTemp < sizeof(xmlbP->self.shortValue))
{
// value is short. Assume it is keywordly-interesting
PCopy(xmlbP->self.shortValue,GlobalTemp);
xmlbP->self.id = GetTokenIdx(tip->aKeywords,xmlbP->self.shortValue);
StringToNum(xmlbP->self.shortValue,&xmlbP->self.numValue);
}
else
{
// value is long. Stash in handle
Accumulator a;
Zero(a);
err = TokenToURL(nil,&a,tip);
if (!err && a.offset<2)
{
AccuZap(a);
err = fnfErr;
}
if (!err)
{
a.offset--;
AccuTrim(&a);
xmlbP->self.longValue = a.data;
}
}
// if the value can have attributes, grab them, too
if (!err)
if (xmlbP->self.type==kElementTag||xmlbP->self.type==kEmptyElementTag)
{
XMLNVP nvp;
for(;!err;)
{
// clear out the old
*nvp.shortValue = 0;
nvp.longValue = 0;
nvp.numValue = 0;
nvp.id = 0;
// grab the new
nvp.type = GetAttribute(tip,GlobalTemp);
// are we done?
if (!nvp.type) break;
nvp.type++; // attributes are off by one
// process the value
if (*GlobalTemp < sizeof(nvp.shortValue))
{
PCopy(nvp.shortValue,GlobalTemp);
StringToNum(nvp.shortValue,&nvp.numValue);
nvp.id = GetTokenIdx(tip->aKeywords,nvp.shortValue);
}
else
{
nvp.longValue = NuHandle(*GlobalTemp+1);
if (nvp.longValue) PCopy(*nvp.longValue,GlobalTemp);
else
err = MemError();
}
// and put it on the stack
if (xmlbP->attribs || !(err=StackInit(sizeof(nvp),&xmlbP->attribs)))
err = StackPush(&nvp,xmlbP->attribs);
}
}
// ok, now the value is all safely tucked away, let's see what it is
if (err || xmlbP->self.type!=kElementTag)
// easy! We're done!
;
else
{
XMLBinary xmlb;
// We need to fetch the contents of the element
do
{
Zero(xmlb);
if (!(err=XMLParseElement(tip,&xmlb)))
{
switch (xmlb.self.type)
{
case kTokenDone:
// we ran out of tokens before the closing tag
err = eofErr;
break;
// Stuff that goes in the contents
case kElementTag:
case kContent:
case kEmptyElementTag:
if (!xmlbP->contents && (err=StackInit(sizeof(xmlb),&xmlbP->contents))) break;
err = StackPush(&xmlb,xmlbP->contents);
break;
// An end tag; it better be us!
case kEndTag:
if (xmlb.self.id != xmlbP->self.id)
err = paramErr;
break;
case kCommentTag:
ZapHandle(xmlb.self.longValue); // if we have a value, we don't want it
break;
default:
ASSERT(0);
// we don't belong here!
err = paramErr;
break;
}
}
}
while (!err && xmlb.self.type != kEndTag);
XMLBinDispose(&xmlb);
}
// Whew! Did we win?
if (err)
{
XMLBinDispose(xmlbP);
Zero(*xmlbP);
}
else
{
StackCompact(xmlbP->attribs);
StackCompact(xmlbP->contents);
}
return err;
}
/************************************************************************
* XMLBinStackDispose - dispose of a stackful of xmlb's
************************************************************************/
void XMLBinStackDispose(StackHandle stack)
{
XMLBinary xmlb;
while (!StackPop(&xmlb,stack)) XMLBinDispose(&xmlb);
DisposeHandle(stack);
}
/************************************************************************
* XMLBinDispose - dispose the contents of an xml binary
************************************************************************/
void XMLBinDispose(XMLBinPtr xmlbP)
{
XMLBinStackDispose(xmlbP->contents);
XMLNVPStackDispose(xmlbP->attribs);
XMLNVPDispose(&xmlbP->self);
xmlbP->contents = nil;
xmlbP->attribs = nil;
}
/************************************************************************
* XMLNVPStackDispose - dispose of a stackful of nvp's
************************************************************************/
void XMLNVPStackDispose(StackHandle stack)
{
XMLNVP nvp;
while (!StackPop(&nvp,stack)) XMLNVPDispose(&nvp);
DisposeHandle(stack);
}
/************************************************************************
* XMLNVPDispose - dispose the contents of an xml binary
************************************************************************/
void XMLNVPDispose(XMLNVPPtr nvpP)
{
ZapHandle(nvpP->longValue);
}
#ifdef DEBUG
/************************************************************************
* XMLBinStackPrint - print out a stack of binary xml
************************************************************************/
OSErr XMLBinStackPrint(AccuPtr a,StackHandle xmlbStack)
{
OSErr err = noErr;
if (!xmlbStack) return noErr;
else
{
short i;
short n = (*xmlbStack)->elCount;
XMLBinary xmlb;
for (i=0;i<n;i++)
{
StackItem(&xmlb,i,xmlbStack);
if (err = XMLBinPrint(a,&xmlb))
break;
}
}
return err;
}
/************************************************************************
* XMLBinPrint - print an individual xml binary
************************************************************************/
OSErr XMLBinPrint(AccuPtr a,XMLBinPtr xmlbP)
{
OSErr err;
err = XMLNVPPrint(a,&xmlbP->self);
if (!err)
if (xmlbP->attribs)
{
XMLIncIndent();
err = XMLNVPStackPrint(a,xmlbP->attribs);
XMLDecIndent();
}
if (!err)
if (xmlbP->contents)
{
XMLIncIndent();
err = XMLBinStackPrint(a,xmlbP->contents);
XMLDecIndent();
}
return err;
}
/************************************************************************
* XMLNVPStackPrint - print a stack of xml name-value pairs
************************************************************************/
OSErr XMLNVPStackPrint(AccuPtr a,StackHandle nvpStack)
{
OSErr err = noErr;
if (!nvpStack) return noErr;
else
{
short i;
short n = (*nvpStack)->elCount;
XMLNVP nvp;
for (i=0;i<n;i++)
{
StackItem(&nvp,i,nvpStack);
if (err = XMLNVPPrint(a,&nvp))
break;
}
}
return err;
}
/************************************************************************
* XMLNVPPrint - print an individual xml name-value pair
************************************************************************/
OSErr XMLNVPPrint(AccuPtr a,XMLNVPPtr nvpP)
{
OSErr err = noErr;
AccuIndent(a); AccuCompose(a,"\pty: %d\015",nvpP->type);
if (nvpP->id)
{AccuIndent(a); AccuCompose(a,"\pid: %d\015",nvpP->id);}
if (nvpP->numValue)
{AccuIndent(a); AccuCompose(a,"\p#: %d\015",nvpP->numValue);}
if (*nvpP->shortValue)
{AccuIndent(a); AccuCompose(a,"\psv: %p\015",nvpP->shortValue);}
if (nvpP->longValue)
{AccuIndent(a); AccuCompose(a,"\plv: %x\015",nvpP->longValue);}
if (nvpP->userData)
{AccuIndent(a); AccuCompose(a,"\pud: %x\015",nvpP->userData);}
return err; /* Cheap code change to rid of the error */
}
#endif