예제 #1
0
static void sWriteAsXML(ZStrimW_ML& s, const string& iName, const ZTupleValue& iTV)
	{
	switch (iTV.TypeOf())
		{
		case eZType_Vector:
			{
			const vector<ZTupleValue>& v = iTV.GetVector();
			for (vector<ZTupleValue>::const_iterator i = v.begin(); i != v.end(); ++i)
				sWriteAsXML(s, iName, *i);
			break;
			}
		case eZType_Tuple:
			{
			s.Begin(iName);
				sWriteAsXML(s, iTV.GetTuple());
			s.End(iName);
			break;
			}
		case eZType_String:
			{
			s.Begin(iName);
			bool old = s.Indent(false);
				s << iTV.GetString();
			s.End(iName);
			s.Indent(old);
			break;
			}
		case eZType_Null:
			{
			s.Empty(iName);
			break;
			}
		}
	}
예제 #2
0
ZTupleValue ZUtil_BitTorrent::sTupleValueFromStream(const ZStreamU& s)
	{
	ZTupleValue theTV;
	const uint8 type = s.ReadUInt8();
	switch (type)
		{
		case 'd':
			{
			// Dictionary
			ZTuple& theTuple = theTV.SetMutableTuple();
			for (;;)
				{
				if (sTryRead_Byte(s, 'e'))
					break;
				string name = sReadString(s);
				theTuple.SetValue(name, sTupleValueFromStream(s));
				}
			break;
			}
		case 'l':
			{
			// List
			vector<ZTupleValue>& theVector = theTV.SetMutableVector();
			for (;;)
				{
				if (sTryRead_Byte(s, 'e'))
					break;
				theVector.push_back(sTupleValueFromStream(s));
				}
			break;
			}
		case 'i':
			{
			// Integer
			theTV = sReadInteger(s);
			uint8 terminator = s.ReadUInt8();
			if (terminator != 'e')
				throw runtime_error("Expected 'e' terminator for integer");
			break;
			}
		default:
			{
			s.Unread();
			// It must be a 'string'. It could be valid UTF-8, or could be
			// arbitrary bytes, so we call sReadStringish.
			theTV = sReadStringish(s);
			break;
			}
		}
	return theTV;
	}
예제 #3
0
static ZTuple sGetProp(const ZNode& iNode, const string& iPropName)
 	{
 	ZTuple propT;

	if (iPropName == "D:resourcetype")
		{
		if (iNode.CanHaveChildren())
			propT.SetTuple(iPropName, ZTuple().SetNull("D:collection"));
		else
			propT.SetNull(iPropName);
		}
	else if (iPropName == "D:getcontenttype")
		{
		ZTupleValue theValue;
		if (iNode.GetProp("MIMEType", theValue))
			{
			string theMIMEType;
			if (theValue.GetString(theMIMEType))
				propT.SetString(iPropName, theMIMEType);
			}
		}
	else if (iPropName == "D:creationdate")
		{
		ZTupleValue theValue;
		if (iNode.GetProp("TimeCreated", theValue))
			{
			if (ZTime theTime = theValue.GetTime())
				propT.SetString(iPropName, sAsString_WebDAV(theTime));
			}
		}
	else if (iPropName == "D:getlastmodified")
		{
		ZTupleValue theValue;
		if (iNode.GetProp("TimeModified", theValue))
			{
			if (ZTime theTime = theValue.GetTime())
				propT.SetString(iPropName, sAsString_WebDAV(theTime));
			}
		}
	else if (iPropName == "D:getcontentlength")
		{
		ZTupleValue theValue;
		if (iNode.GetProp("ContentLength", theValue))
			{
			int64 theLength;
			if (theValue.GetInt64(theLength))
	 			propT.SetString(iPropName, ZString::sFromUInt64(theLength));
			}
		}

 	return propT;
 	}
예제 #4
0
static bool sIsComplex(const ZUtil_Tuple::Options& iOptions, const ZTupleValue& iTV)
{
    switch (iTV.TypeOf())
    {
    case eZType_Raw:
    {
        size_t theSize;
        iTV.GetRawAttributes(nil, &theSize);
        return theSize > iOptions.fRawChunkSize;
    }
    case eZType_Vector:
        return !iTV.GetVector().empty();
    case eZType_Tuple:
        return !iTV.GetTuple().Empty();
    case eZType_String:
        return sIsComplexString(iOptions, iTV.GetString());
    default:
        return false;
    }
}
예제 #5
0
static void sReadQuotedString_Apos(const ZStrimU& iStrimU, ZTupleValue& oTupleValue)
{
    using namespace ZUtil_Strim;

    string theString;
    theString.reserve(100);
    ZStrimW_String theStrimW(theString);

    for (;;)
    {
        sCopy_EscapedString(iStrimU, '\'', theStrimW);
        if (!sTryRead_CP(iStrimU, '\''))
            throw ParseException("Expected ' to close a string");

        sSkip_WSAndCPlusPlusComments(iStrimU);

        if (!sTryRead_CP(iStrimU, '\''))
            break;
    }

    oTupleValue.SetString(theString);
}
예제 #6
0
bool ZWebDAV::sHandle_GET(const ZTrail& iPrefix, ZNode iRoot, const ZStreamR&, const ZStreamW& iStreamW, const ZTuple& iHeader, const ZTrail& iTrail, const ZTuple& iParam)
	{
	ZNode theNode = iRoot.Trail(iTrail);

	ZHTTP::Response r;
	r.Set("date", sAsString_WebDAV(ZTime::sNow()));

	if (iHeader.Has("range"))
		{
		if (const ZLog::S& s = ZLog::S(ZLog::eInfo, "ZWebDAV"))
			s << "GET with range:\n" << iHeader;
		}

	if (theNode.Exists())
		{
		r.SetResult(200);
		if (theNode.CanHaveChildren())
			{
			r.Set("Content-Type", "text/html; charset=\"utf-8\"");
			r.Set("Transfer-Encoding", "chunked");
			r.Send(iStreamW);

			ZHTTP::StreamW_Chunked chunkedStream(iStreamW);
			ZStrimW_StreamUTF8 theStrimW(chunkedStream);
			ZStrimW_ML s(false, theStrimW);
			s.Begin("html");
				s.Begin("title");
					s << theNode.Name();
				s.End("title");
				s.Begin("body");
					for (ZNodeIter i = theNode; i; i.Advance())
						{
						s.Begin("p");
							s.Begin("a");
								if (i.Current().CanHaveChildren())
									{
									s.Attr("href", ZHTTP::sEncodeComponent(i.Current().Name()) + "/");
									s << i.Current().Name() << "/";
									}
								else
									{
									s.Attr("href", ZHTTP::sEncodeComponent(i.Current().Name()));
									s << i.Current().Name();
									}
							s.End("a");
						s.End("p");
						}
				s.End("body");
			
			s.End("html");
			}
		else if (ZRef<ZStreamerRPos> theStreamer = theNode.OpenRPos())
			{
			const ZStreamRPos& theStreamRPos = theStreamer->GetStreamRPos();
			uint64 sentSize = theStreamRPos.GetSize();

			if (ZTupleValue rangeParam = iHeader.GetValue("range"))
				{
				vector<pair<size_t, size_t> > ranges;
				if (!ZHTTP::sOrganizeRanges(sentSize, rangeParam, ranges))
					{
					iStreamW.WriteString("HTTP/1.1 406 Unsatisfiable range\r\n\r\n");
					return false;
					}
				r.SetResult(206, "Partial Content");

				r.Set("Content-Range", ZString::sFormat("bytes %d-%d/%d", ranges.front().first, ranges.front().second - 1, sentSize));

				theStreamRPos.SetPosition(ranges.front().first);
				sentSize = ranges.front().second - ranges.front().first;
				}
			else
				{
				r.SetResult(200);
				}
			
			string theMIMEType = "application/octet-stream";
			ZTupleValue theMIMEValue;
			if (theNode.GetProp("MIMEType", theMIMEValue))
				{
				string asString;
				if (theMIMEValue.GetString(asString))
					theMIMEType = asString;
				}
			r.Set("Content-Type", theMIMEType);

			ZTupleValue theValue;
			if (theNode.GetProp("lastModified", theValue))
				{
				if (ZTime theTime = theValue.GetTime())
					r.Set("Last-Modified", sAsString_WebDAV(theTime));
				}

			r.Set("Content-Transfer-Encoding", "binary");
			r.Set("Content-Length", ZString::sFromUInt64(sentSize));

			r.Send(iStreamW);

			iStreamW.CopyFrom(theStreamRPos, sentSize);
			}
		}
	else
		{
		r.SetResult(404);
		r.Send(iStreamW);
		r.Set("Content-Length", 0);
		}
	return true;
	}
예제 #7
0
static bool sFromStrim_TupleValue(const ZStrimU& iStrimU, ZTupleValue& oTupleValue)
{
    using namespace ZUtil_Strim;

    sSkip_WSAndCPlusPlusComments(iStrimU);

    if (sTryRead_CP(iStrimU, '['))
    {
        sFromStrim_BodyOfVector(iStrimU, oTupleValue.SetMutableVector());

        sSkip_WSAndCPlusPlusComments(iStrimU);

        if (!sTryRead_CP(iStrimU, ']'))
            throw ParseException("Expected ']' to close a vector");
    }
    else if (sTryRead_CP(iStrimU, '{'))
    {
        // It's a tuple.
        sFromStrim_BodyOfTuple(iStrimU, oTupleValue.SetMutableTuple());

        sSkip_WSAndCPlusPlusComments(iStrimU);

        if (!sTryRead_CP(iStrimU, '}'))
            throw ParseException("Expected '}' to close a tuple");
    }
    else if (sTryRead_CP(iStrimU, '"'))
    {
        // It's a string, delimited by ".
        sReadQuotedString_Quote(iStrimU, oTupleValue);
    }
    else if (sTryRead_CP(iStrimU, '\''))
    {
        // It's a string, delimited by '.
        sReadQuotedString_Apos(iStrimU, oTupleValue);
    }
    else if (sTryRead_CP(iStrimU, '('))
    {
        // It's a raw.
        ZMemoryBlock theMemoryBlock;
        ZStreamR_HexStrim(iStrimU).CopyAllTo(ZStreamRWPos_MemoryBlock(theMemoryBlock));
        sSkip_WSAndCPlusPlusComments(iStrimU);

        if (!sTryRead_CP(iStrimU, ')'))
            throw ParseException("Expected ')' to close a raw");
        oTupleValue.SetRaw(theMemoryBlock);
    }
    else
    {
        string theTypeLC, theType;
        if (!ZUtil_Tuple::sRead_Identifier(iStrimU, &theTypeLC, &theType))
        {
            // We couldn't find any of the special characters, nor could
            // we read a type designator, so we fail to read a tuplevalue,
            // which is not a parse error at this stage -- it might be for our caller.
            return false;
        }

        if (theTypeLC == "null")
        {
            oTupleValue.SetNull();
        }
        else if (theTypeLC == "false")
        {
            oTupleValue.SetBool(false);
        }
        else if (theTypeLC == "true")
        {
            oTupleValue.SetBool(true);
        }
        else
        {
            sSkip_WSAndCPlusPlusComments(iStrimU);
            if (!sTryRead_CP(iStrimU, '('))
                throw ParseException("Expected '(' following a type designator");

            sSkip_WSAndCPlusPlusComments(iStrimU);

            if (theTypeLC == "type")
            {
                string typeValueLC, typeValue;
                if (!ZUtil_Tuple::sRead_Identifier(iStrimU, &typeValueLC, &typeValue))
                    throw ParseException("Expected a type name");

                if (typeValueLC == "null") oTupleValue.SetType(eZType_Null);
                else if (typeValueLC == "string") oTupleValue.SetType(eZType_String);
                else if (typeValueLC == "cstring") oTupleValue.SetType(eZType_CString);
                else if (typeValueLC == "int8") oTupleValue.SetType(eZType_Int8);
                else if (typeValueLC == "int16") oTupleValue.SetType(eZType_Int16);
                else if (typeValueLC == "int32") oTupleValue.SetType(eZType_Int32);
                else if (typeValueLC == "int64") oTupleValue.SetType(eZType_Int64);
                else if (typeValueLC == "float") oTupleValue.SetType(eZType_Float);
                else if (typeValueLC == "double") oTupleValue.SetType(eZType_Double);
                else if (typeValueLC == "time") oTupleValue.SetType(eZType_Time);
                else if (typeValueLC == "bool") oTupleValue.SetType(eZType_Bool);
                else if (typeValueLC == "pointer") oTupleValue.SetType(eZType_Pointer);
                else if (typeValueLC == "raw") oTupleValue.SetType(eZType_Raw);
                else if (typeValueLC == "tuple") oTupleValue.SetType(eZType_Tuple);
                else if (typeValueLC == "refcounted") oTupleValue.SetType(eZType_RefCounted);
                else if (typeValueLC == "rect") oTupleValue.SetType(eZType_Rect);
                else if (typeValueLC == "point") oTupleValue.SetType(eZType_Point);
                else if (typeValueLC == "region") oTupleValue.SetType(eZType_Region);
                else if (typeValueLC == "id") oTupleValue.SetType(eZType_ID);
                else if (typeValueLC == "vector") oTupleValue.SetType(eZType_Vector);
                else if (typeValueLC == "type") oTupleValue.SetType(eZType_Type);
                else if (typeValueLC == "time") oTupleValue.SetType(eZType_Time);
                else
                    throw ParseException("Unknown type name '" + typeValue + "'");
            }
            else if (theTypeLC == "id")
            {
                oTupleValue.SetID(sMustRead_GenericInteger(iStrimU));
            }
            else if (theTypeLC == "int8")
            {
                oTupleValue.SetInt8(sMustRead_GenericInteger(iStrimU));
            }
            else if (theTypeLC == "int16")
            {
                oTupleValue.SetInt16(sMustRead_GenericInteger(iStrimU));
            }
            else if (theTypeLC == "int32")
            {
                oTupleValue.SetInt32(sMustRead_GenericInteger(iStrimU));
            }
            else if (theTypeLC == "int64")
            {
                oTupleValue.SetInt64(sMustRead_GenericInteger(iStrimU));
            }
            else if (theTypeLC == "bool")
            {
                string theBool;
                if (!ZUtil_Tuple::sRead_Identifier(iStrimU, &theBool, nil))
                    throw ParseException("Expected 'false' or 'true'");

                if (theBool == "true")
                    oTupleValue.SetBool(true);
                else if (theBool == "false")
                    oTupleValue.SetBool(false);
                else
                    throw ParseException("Expected 'false' or 'true'");
            }
            else if (theTypeLC == "float")
            {
                double theDouble;
                if (!sTryRead_SignedDouble(iStrimU, theDouble))
                    throw ParseException("Expected a floating point number");
                oTupleValue.SetFloat(theDouble);
            }
            else if (theTypeLC == "double")
            {
                double theDouble;
                if (!sTryRead_SignedDouble(iStrimU, theDouble))
                    throw ParseException("Expected a floating point number");
                oTupleValue.SetDouble(theDouble);
            }
            else if (theTypeLC == "time")
            {
                if (sTryRead_CP(iStrimU, ')'))
                {
                    // It's a time with no content, hence an invalid time.
                    oTupleValue.SetTime(ZTime());

                    // We'll take an early exit so the normal code that
                    // looks for a closing parenthesis doesn't choke.
                    return true;
                }
                // Try to read a double, which is how we're representing
                // times in text streams for now.
                double theDouble;
                if (!sTryRead_SignedDouble(iStrimU, theDouble))
                    throw ParseException("Expected a floating point time");
                oTupleValue.SetTime(theDouble);
            }
            else if (theTypeLC == "rect")
            {
                ZRectPOD theRect;

                theRect.left = sMustRead_GenericInteger(iStrimU);

                sMustRead_WSCommaWS(iStrimU);

                theRect.top = sMustRead_GenericInteger(iStrimU);

                sMustRead_WSCommaWS(iStrimU);

                theRect.right = sMustRead_GenericInteger(iStrimU);

                sMustRead_WSCommaWS(iStrimU);

                theRect.bottom = sMustRead_GenericInteger(iStrimU);

                oTupleValue.SetRect(theRect);
            }
            else if (theTypeLC == "point")
            {
                ZPointPOD thePoint;

                thePoint.h = sMustRead_GenericInteger(iStrimU);

                sMustRead_WSCommaWS(iStrimU);

                thePoint.v = sMustRead_GenericInteger(iStrimU);

                oTupleValue.SetPoint(thePoint);
            }
            else
            {
                throw ParseException("Unknown type designator '" + theType + "'");
            }

            sSkip_WSAndCPlusPlusComments(iStrimU);

            if (!sTryRead_CP(iStrimU, ')'))
                throw ParseException("Expected ')' to close a value");
        }
    }
    return true;
}
예제 #8
0
static void sReadQuotedString_Quote(const ZStrimU& iStrimU, ZTupleValue& oTupleValue)
{
    using namespace ZUtil_Strim;

    string theString;
    theString.reserve(100);
    ZStrimW_String theStrimW(theString);

    for (;;)
    {
        // We've read, and could un-read, a quote mark.
        if (sTryRead_CP(iStrimU, '"'))
        {
            // We've now seen a second quote, abutting the first.
            if (sTryRead_CP(iStrimU, '"'))
            {
                // We have three quotes in a row, which opens a verbatim string.
                // If the next character is an EOL then absorb it, so the verbatim
                // text can start on a fresh line, but not be parsed as
                // beginning with an EOL.
                UTF32 theCP = iStrimU.ReadCP();
                if (!ZUnicode::sIsEOL(theCP))
                    iStrimU.Unread();

                // Now copy everything till we see three quotes in a row again.
                ZStrimR_Boundary theStrimR_Boundary("\"\"\"", iStrimU);
                theStrimW.CopyAllFrom(theStrimR_Boundary);
                if (!theStrimR_Boundary.HitBoundary())
                    throw ParseException("Expected \"\"\" to close a string");

                if (sTryRead_CP(iStrimU, '"'))
                {
                    // We have another quote, so there were at least four in a row,
                    // which we get with a quote in the text immediately followed
                    // by the triple quote. So emit a quote.
                    theStrimW.WriteCP('"');
                    if (sTryRead_CP(iStrimU, '"'))
                    {
                        // Same again -- five quotes in a row, which is two content
                        // quotes followed by the closing triple.
                        theStrimW.WriteCP('"');
                        // This is why it's essential that when using triple quotes
                        // you put whitespace before the opening, and after the closing
                        // triple, so we don't mistake included quotes for ones that
                        // are (say) opening a subsequent regular quoted sequence.
                    }
                }
            }
            else
            {
                // We have two quotes in a row, followed by something else, so
                // we had an empty string segment.
            }
        }
        else
        {
            sCopy_EscapedString(iStrimU, '"', theStrimW);
            if (!sTryRead_CP(iStrimU, '"'))
                throw ParseException("Expected \" to close a string");
        }

        sSkip_WSAndCPlusPlusComments(iStrimU);

        if (!sTryRead_CP(iStrimU, '"'))
            break;
    }

    oTupleValue.SetString(theString);
}
예제 #9
0
static void sToStrim_TupleValue(const ZStrimW& s, const ZTupleValue& iTV,
                                size_t iLevel, const ZUtil_Tuple::Options& iOptions, bool iMayNeedInitialLF)
{
    switch (iTV.TypeOf())
    {
    case eZType_Vector:
    {
        sToStrim_Vector(s, iTV.GetVector(), iLevel, iOptions, iMayNeedInitialLF);
        break;
    }
    case eZType_Tuple:
    {
        sToStrim_Tuple(s, iTV.GetTuple(), iLevel, iOptions, iMayNeedInitialLF);
        break;
    }
    case eZType_Raw:
    {
        const void* theData;
        size_t theSize;
        iTV.GetRawAttributes(&theData, &theSize);
        if (theSize == 0)
        {
            // we've got an empty Raw
            s.Write("()");
        }
        else
        {
            ZStreamRPos_Memory dataStream(theData, theSize);

            if (iOptions.DoIndentation() && theSize > iOptions.fRawChunkSize)
            {
                if (iMayNeedInitialLF)
                    sWriteLFIndent(s, iLevel, iOptions);

                s.Writef("( // %d bytes", theSize);
                sWriteLFIndent(s, iLevel, iOptions);
                if (iOptions.fRawAsASCII)
                {
                    for (;;)
                    {
                        uint64 lastPos = dataStream.GetPosition();
                        uint64 countCopied;
                        ZStreamW_HexStrim(iOptions.fRawByteSeparator, "", 0, s)
                        .CopyFrom(dataStream, iOptions.fRawChunkSize, &countCopied, nil);

                        if (countCopied == 0)
                            break;

                        dataStream.SetPosition(lastPos);
                        if (size_t extraSpaces = iOptions.fRawChunkSize - countCopied)
                        {
                            // We didn't write a complete line of bytes, so pad it out.
                            while (extraSpaces--)
                            {
                                // Two spaces for the two nibbles
                                s.Write("  ");
                                // And then the separator sequence
                                s.Write(iOptions.fRawByteSeparator);
                            }
                        }

                        s.Write(" // ");
                        while (countCopied--)
                        {
                            char theChar = dataStream.ReadInt8();
                            if (theChar < 0x20 || theChar > 0x7E)
                                s.WriteCP('.');
                            else
                                s.WriteCP(theChar);
                        }
                        sWriteLFIndent(s, iLevel, iOptions);
                    }
                }
                else
                {
                    string eol = iOptions.fEOLString;
                    for (size_t x = 0; x < iLevel; ++x)
                        eol += iOptions.fIndentString;

                    ZStreamW_HexStrim(iOptions.fRawByteSeparator,
                                      eol, iOptions.fRawChunkSize, s).CopyAllFrom(dataStream);

                    sWriteLFIndent(s, iLevel, iOptions);
                }

                s.Write(")");
            }
            else
            {
                s.Write("(");

                ZStreamW_HexStrim(iOptions.fRawByteSeparator, "", 0, s)
                .CopyAllFrom(dataStream);

                if (iOptions.fRawAsASCII)
                {
                    dataStream.SetPosition(0);
                    s.Write(" /* ");
                    while (theSize--)
                    {
                        char theChar = dataStream.ReadInt8();
                        if (theChar < 0x20 || theChar > 0x7E)
                            s.WriteCP('.');
                        else
                            s.WriteCP(theChar);
                    }
                    s.Write(" */");
                }
                s.Write(")");
            }
        }
        break;
    }
    default:
    {
        // We've got something other than a tuple or a vector.
        sSimpleTupleValueToStrim(s, iTV, iLevel, iOptions);
        break;
    }
    }
}
예제 #10
0
static void sSimpleTupleValueToStrim(const ZStrimW& s, const ZTupleValue& iTV,
                                     size_t iLevel, const ZUtil_Tuple::Options& iOptions)
{
    switch (iTV.TypeOf())
    {
    case eZType_Null:
        s.Write("Null");
        break;
    case eZType_Type:
    {
        s.Write("Type(");
        s.Write(ZTypeAsString(iTV.GetType()));
        s.Write(")");
        break;
    }
    case eZType_ID:
    {
        s.Writef("ID(0x%0llX)", iTV.GetID());
        if (iOptions.fIDsHaveDecimalVersionComment)
            s.Writef(" /* %lld */", iTV.GetID());
        break;
    }
    case eZType_Int8:
        s.Writef("int8(%d)", iTV.GetInt8());
        break;
    case eZType_Int16:
        s.Writef("int16(%d)", iTV.GetInt16());
        break;
    case eZType_Int32:
        s.Writef("int32(%d)", iTV.GetInt32());
        break;
    case eZType_Int64:
        s.Writef("int64(0x%0llX)", iTV.GetInt64());
        break;
    case eZType_Bool:
    {
        if (iTV.GetBool())
            s.Write("true");
        else
            s.Write("false");
        break;
    }
    case eZType_Float:
    {
        // 9 decimal digits are necessary and sufficient for single precision IEEE 754.
        // "What Every Computer Scientist Should Know About Floating Point", Goldberg, 1991.
        // <http://docs.sun.com/source/806-3568/ncg_goldberg.html>
        s.Writef("float(%.9g)", iTV.GetFloat());
        break;
    }
    case eZType_Double:
    {
        // 17 decimal digits are necessary and sufficient for double precision IEEE 754.
        s.Writef("double(%.17g)", iTV.GetDouble());
        break;
    }
    case eZType_Time:
    {
        // For the moment I'm just writing times as a count of seconds, putting
        // the broken-out Gregorian version in a comment. Later we can improve
        // the parsing of dates, and then we can write them in human readable form.
        if (ZTime theTime = iTV.GetTime())
        {
            s.Writef("time(%.17g)", theTime.fVal);
            if (iOptions.fTimesHaveUserLegibleComment)
            {
                s << " /*" << ZUtil_Time::sAsStringUTC(theTime, "%Y-%m-%dZ%H:%M:");
                // We've got about 10 significant digits in year (-10,000 to +10,000),
                // month, day, hour, minutes, and seconds, and no more than 17 in a double.
                // To get a leading zero in the seconds' tens column we add 100
                // to the count of seconds, so to get up to 7 digits in the fraction we
                // need to allow ten digits overall.
                s << ZString::sFormat("%.10g", 100.0 + fmod(theTime.fVal, 60)).substr(1);
                s << "*/";
            };
        }
        else
        {
            // We're now allowing empty parens to represent invalid times.
            s.Write("time()");
        }
        break;
    }
    case eZType_Pointer:
        s.Writef("pointer(%08X)", iTV.GetPointer());
        break;
    case eZType_Rect:
    {
        const ZRectPOD& theRect = iTV.GetRect();
        s.Writef("Rect(%d, %d, %d, %d)",
                 theRect.left,
                 theRect.top,
                 theRect.right,
                 theRect.bottom);
        break;
    }
    case eZType_Point:
    {
        const ZPointPOD& thePoint = iTV.GetPoint();
        s.Writef("Point(%d, %d)",
                 thePoint.h,
                 thePoint.v);
        break;
    }
    case eZType_String:
    {
        const string& theString = iTV.GetString();
        if (iOptions.fBreakStrings && iOptions.DoIndentation())
        {
            if (string::npos != theString.find_first_of("\n\r"))
            {
                // We put a newline after the opening """, which will be
                // ignored by sFromStrim, so the first line of theString
                // will be in column zero.
                s << "\"\"\"\n";
                ZStrimU_String strim_String(theString);
                ZStrimR_Boundary strim_Boundary("\"\"\"", strim_String);
                for (;;)
                {
                    s.CopyAllFrom(strim_Boundary);
                    if (!strim_Boundary.HitBoundary())
                    {
                        // We've returned without having hit the boundary, so we're done.
                        break;
                    }
                    strim_Boundary.Reset();

                    // Close the triple quotes.
                    s << "\"\"\"";
                    // A space to separate the triple-quote from the single quote
                    s << " ";
                    // An open quote
                    s << "\"";
                    // Three escaped quotes.
                    s << "\\\"\\\"\\\"";
                    // A close quote.
                    s << "\"";
                    // Another space, for symmetry
                    s << " ";
                    // And re-open triple quotes again.
                    s << "\"\"\"";
                    // With a newline, so the text will again
                    // start in column zero.
                    s << "\n";
                }
                s << "\"\"\"";
                break;
            }
        }
        string delimiter = "\"";
        bool quoteQuotes = true;
        if (string::npos != theString.find('"') && string::npos == theString.find('\''))
        {
            delimiter = "'";
            quoteQuotes = false;
        }

        s.Write(delimiter);

        ZStrimW_Escapify::Options theOptions;
        theOptions.fQuoteQuotes = quoteQuotes;
        theOptions.fEscapeHighUnicode = false;

        ZStrimW_Escapify(theOptions, s).Write(theString);

        s.Write(delimiter);
        break;
    }
    case eZType_RefCounted:
        s.Writef("RefCounted(%08X)", iTV.GetRefCounted().GetObject());
        break;
    case eZType_Raw:
    case eZType_Tuple:
    case eZType_Vector:
    {
        ZDebugStopf(0,
                    ("sSimpleTupleValueToStrim should only be called on simple tuple values"));
        break;
    }
    default:
    {
        ZDebugStopf(0, ("Unrecognized type %d", iTV.TypeOf()));
        break;
    }
    }
}