Exemplo n.º 1
0
/*----------------------------------------------------------------------------------------------
	Crawl through the database and rename/delete the given styles.
----------------------------------------------------------------------------------------------*/
STDMETHODIMP FwDbMergeStyles::Process(DWORD hWnd)
{
	BEGIN_COM_METHOD;
	Assert(m_vstuOldNames.Size() == m_vstuNewNames.Size());

	if (!m_hvoRoot)
		return E_UNEXPECTED;
	if (!m_pclsidApp)
		return E_UNEXPECTED;

	if (LogFile())
	{
		ULONG cbLog;
		StrAnsi staLog;
		staLog.Format("Changing style names in %S (%S)%n",
			DbName().Chars(), ServerName().Chars());
		LogFile()->Write(staLog.Chars(), staLog.Length(), &cbLog);
	}

	m_qprog->DoModeless((HWND)hWnd);
	StrUni stuMsg(kstidChgStyleLabel);
	m_qprog->put_Title(stuMsg.Bstr());

	ResetConnection();
	BeginTrans();
	CreateCommand();

	SetPercentComplete(0);

	// We want to affect only a subset of the formatting and style information in the database,
	// based on which program is currently running, since different programs may be using
	// different sets of styles.  This information is derived from m_hvoRoot and m_pclsidApp.
	// (Unfortunately, there's no way to derive one of these values from the other one.)  The
	// current scheme works for Data Notebook, List Editor, TE, and LexText.  Additional
	// programs may introduce even more complexities.
	// 1. Find all the owners of StStyle objects.  This should match up to LangProject (Data
	//    Notebook and List Editor), Scripture (TE), or LexDb (LexText).
	// 2. If an owner equals m_hvoRoot, then only those objects owned by m_hvoRoot have the
	//    string and paragraph formatting fixed.  Except that LexText also wants to fix all the
	//    Text objects owned by the LangProject in addition to all the objects owned by the
	//    LexDb, so those have to be added to the collection of objects.
	// 3. If none of the owners equals m_hvoRoot, we must be dealing with Data Notebook or List
	//    Editor, which share a common set of styles owned by the LangProject itself.  In
	//    this case, we want all the objects owned by the LangProject except those owned by
	//    another owner of styles (or the LangProject Text objects, since those use the
	//    LexText styles even though they're not owned by the root object).  (This isn't quite
	//    right if either TE or LexText does not actually own any styles yet, but we won't worry
	//    about this nicety.)
	// 4. After creating a temp table containing just the ids of those objects of interest, we
	//    can then process all the relevant string formatting (via a string crawler) and
	//    paragraph StyleRules (later in this method)
	// 5. Finally, we may need to fix the Style fields of the relevant UserViewField objects.
	//    These are limited by the psclsidApp argument, since the UserView objects are specific
	//    to specific applications.  If pssclidApp indicates either Data Notebook or List
	//    Editor, then UserViewField objects belonging to both of those programs are fixed.
	//    Otherwise, only those UserViewField objects belonging to the specific program
	//    identified by psclsidApp are fixed.

	ComBool fMoreRows;
	UINT cbSpaceTaken;
	ComBool fIsNull;
	int hobj = 0;
	int clid = 0;
	bool fFixOwned = false;
	int clidOwned = 0;
	int hvoLangProj = 0;
	Vector<int> vhvoOwners;
	Vector<int> vclidOwners;
	Vector<int> vhvoTexts;
	StrUni stuCmd;
	stuCmd.Format(L"SELECT DISTINCT c.Owner$, co.Class$ "
		L"FROM CmObject c "
		L"JOIN CmObject co on co.Id = c.Owner$ "
		L"WHERE c.Class$ = %d",
		kclidStStyle);
	CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtSelectWithOneRowset));
	CheckHr(GetOleDbCommand()->GetRowset(0));
	CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));
	while (fMoreRows)
	{
		CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&hobj),
			isizeof(hobj), &cbSpaceTaken, &fIsNull, 0));
		CheckHr(GetOleDbCommand()->GetColValue(2, reinterpret_cast <BYTE *>(&clid),
			isizeof(clid), &cbSpaceTaken, &fIsNull, 0));
		if (hobj == m_hvoRoot)
		{
			fFixOwned = true;
			clidOwned = clid;
		}
		else if (clid == kclidLangProject)
		{
			hvoLangProj = hobj;
		}
		else
		{
			vhvoOwners.Push(hobj);
			vclidOwners.Push(clid);
		}
		CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));
	}
	Assert(hvoLangProj != 0);
// This may not be defined in any of our header files.
#ifndef kclidLexDb
#define kclidLexDb 5005
#endif
	if (!fFixOwned || clidOwned == kclidLexDb)
	{
		// We need the set of LangProject_Texts objects to include or exclude.
		stuCmd.Format(L"SELECT Dst FROM LangProject_Texts WHERE Src = %d", hvoLangProj);
		CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(),
			knSqlStmtSelectWithOneRowset));
		CheckHr(GetOleDbCommand()->GetRowset(0));
		CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));
		while (fMoreRows)
		{
			CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&hobj),
				isizeof(hobj), &cbSpaceTaken, &fIsNull, 0));
			vhvoTexts.Push(hobj);
			CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));
		}
	}
	// Note that dbo.fnGetOwnedObjects$() returns the root object as the first row.
	stuCmd.Format(L"CREATE TABLE #OwnedObjIdsTbl%n"
		L"(%n"
		L"    ObjId int not null%n"
		L")%n"
		L"CREATE CLUSTERED INDEX #OwnedObjIdsTblObjId ON #OwnedObjIdsTbl ([ObjId])%n");
	CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults));

	const OLECHAR * kpszDefTableFmt =
		L"INSERT INTO #OwnedObjIdsTbl%n"
		L"SELECT ObjId%n"
		L"FROM dbo.fnGetOwnedObjects$('%d', null, 0, 0, 1, null, 0)";

	if (fFixOwned)
	{
		stuCmd.Format(kpszDefTableFmt, m_hvoRoot);
		stuCmd.FormatAppend(L";%n");
		if (vhvoTexts.Size())
		{
			stuCmd.FormatAppend(
				L"INSERT INTO #OwnedObjIdsTbl%n"
				L"SELECT ObjId%n"
				L"FROM dbo.fnGetOwnedObjects$('");
			for (int ihvo = 0; ihvo < vhvoTexts.Size(); ++ihvo)
				if (ihvo == 0)
					stuCmd.FormatAppend(L"%d", vhvoTexts[ihvo]);
				else
					stuCmd.FormatAppend(L",%d", vhvoTexts[ihvo]);
			stuCmd.FormatAppend(L"', null, 0, 0, 1, null, 0);");
		}
	}
	else
	{
	/*
	POSSIBLE SPEED ENHANCEMENT
	--------------------------
	SELECT co.Id
	FROM CmObject co
	WHERE co.Owner$=1 AND co.OwnFlid$ NOT IN (6001006, 6001014, 6001040)

	gives a list of ids which own what we want to look through.  This could then be
	handled by the following query, which runs in one-half to one-third of the time
	required by the current code.

	SELECT 1 UNION SELECT ObjId FROM dbo.fnGetOwnedObjects$('2,54728', null, 0, 0, 1, null, 0)

	whether the C++ code or the SQL code should put together the XML string is a good
	question.
	*/
		// REVIEW (SteveMiller): The temp tables below just slow things down.

		stuCmd.Clear();
		if (vhvoOwners.Size() || vhvoTexts.Size())
		{
			stuCmd.FormatAppend(L"CREATE TABLE #tblUnwanted ( ObjId int not null );%n");
			stuCmd.FormatAppend(L"INSERT INTO #tblUnwanted%n");
			stuCmd.FormatAppend(L"SELECT ObjId%n");
			stuCmd.FormatAppend(L"FROM dbo.fnGetOwnedObjects$('");
			for (int ihvo = 0; ihvo < vhvoOwners.Size(); ++ihvo)
				if (ihvo == 0)
					stuCmd.FormatAppend(L"%d", vhvoOwners[ihvo]);
				else
					stuCmd.FormatAppend(L",%d", vhvoOwners[ihvo]);
			for (int ihvo = 0; ihvo < vhvoTexts.Size(); ++ihvo)
				if (vhvoOwners.Size() < 0)
					// I don't think we can have an ownerless text, but hey...
					stuCmd.FormatAppend(L"%d", vhvoTexts[ihvo]);
				else
					stuCmd.FormatAppend(L",%d", vhvoTexts[ihvo]);
			stuCmd.FormatAppend(L"',null,0,0,1,null,0);%n");
		}
		stuCmd.FormatAppend(kpszDefTableFmt, hvoLangProj);
		if (vhvoOwners.Size() || vhvoTexts.Size())
		{
			stuCmd.FormatAppend(L"WHERE ObjId NOT IN (SELECT ObjId FROM #tblUnwanted);%n");
			stuCmd.FormatAppend(L"DROP TABLE #tblUnwanted");
		}
		stuCmd.FormatAppend(L";%n");	// terminate for Firebird
	}
	CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults));

	// Do the standard stuff for monolingual and multilingual strings.
	DoAll(kstidChgStylePhaseOne, kstidChgStylePhaseTwo,
		false,	// don't create a new transaction--this method handles it
		L"#OwnedObjIdsTbl");

	// Fix all the StyleRules of instances of StPara.
	stuMsg.Load(kstidChgStylePhaseThree);
	m_qprog->put_Message(stuMsg.Bstr());

	int nPercent = 0;
	SetPercentComplete(nPercent);

	Vector<int> vhobjFix;
	Vector<Vector<byte> > vvbFmtFix;
	vhobjFix.Clear();
	vvbFmtFix.Clear();

	Vector<byte> vbFmt;
	int cbFmt;

	ITsPropsFactoryPtr qtpf;
	qtpf.CreateInstance(CLSID_TsPropsFactory);

	int cRows;
	StrUni stuCmdCnt;
	stuCmdCnt.Format(L"SELECT COUNT(*) FROM StPara a%n"
		L"JOIN #OwnedObjIdsTbl b on b.ObjId = a.Id%n"
		L"WHERE a.StyleRules IS NOT NULL",
		m_hvoRoot);
	CheckHr(GetOleDbCommand()->ExecCommand(stuCmdCnt.Bstr(),
		knSqlStmtSelectWithOneRowset));
	CheckHr(GetOleDbCommand()->GetRowset(0));
	CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));
	CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&cRows),
		isizeof(hobj), &cbSpaceTaken, &fIsNull, 0));

	stuCmd.Format(L"SELECT a.Id, a.StyleRules FROM StPara a%n"
		L"JOIN #OwnedObjIdsTbl b on b.ObjId = a.Id%n"
		L"WHERE a.StyleRules IS NOT NULL",
		m_hvoRoot);
	CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtSelectWithOneRowset));
	CheckHr(GetOleDbCommand()->GetRowset(0));
	CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));
	int iRow = 0;
	ComVector<ITsTextProps> vqttp;
	vqttp.Resize(1);
	while (fMoreRows)
	{
		CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&hobj),
			isizeof(hobj), &cbSpaceTaken, &fIsNull, 0));
		CheckHr(GetOleDbCommand()->GetColValue(2,
			reinterpret_cast <BYTE *>(vbFmt.Begin()), vbFmt.Size(), &cbSpaceTaken, &fIsNull,
			0));
		cbFmt = cbSpaceTaken;
		if (cbFmt >= vbFmt.Size())
		{
			vbFmt.Resize(cbFmt + 1);
			CheckHr(GetOleDbCommand()->GetColValue(2,
				reinterpret_cast <BYTE *>(vbFmt.Begin()),
				vbFmt.Size(), &cbSpaceTaken, &fIsNull, 0));
			cbFmt = cbSpaceTaken;
		}
		vbFmt.Resize(cbFmt);

		ITsTextPropsPtr qttp;
		bool fModify = false;
		int cb = vbFmt.Size();
		CheckHr(qtpf->DeserializePropsRgb(vbFmt.Begin(), &cb, (ITsTextProps **)&qttp));

		// use a vector with exactly 1 item
		vqttp[0] = qttp;
		// Note: using "Normal" doesn't work all the time. For more complex scenarios where the
		// default style depends on the context of the deleted style, a replace should be done
		// instead of a delete. See FwStylesDlg.DeleteAndRenameStylesInDB() in FwStylesDlg.cs.
		fModify = ProcessFormatting(vqttp, g_pszwStyleNormal);
		if (fModify)
		{
			vhobjFix.Push(hobj);
			int cbNeeded;
			HRESULT hr;
			CheckHr(hr = vqttp[0]->SerializeRgb(vbFmt.Begin(), vbFmt.Size(), &cbNeeded));
			if (hr == S_FALSE)
			{
				vbFmt.Resize(cbNeeded);
				hr = vqttp[0]->SerializeRgb(vbFmt.Begin(), vbFmt.Size(), &cbNeeded);
			}
			vbFmt.Resize(cbNeeded);
			vvbFmtFix.Push(vbFmt);
		}
		CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));

		iRow++;
		SetPercentComplete((iRow * 50) / cRows);
	}
	int ceFix = vhobjFix.Size();
	SetPercentComplete(50);
	for (int ieFix = 0; ieFix < ceFix; ++ieFix)
	{
		stuCmd.Format(L"UPDATE StPara SET StyleRules=? WHERE [Id] = %d", vhobjFix[ieFix]);
		// Set the parameter and execute the command.
		CheckHr(GetOleDbCommand()->SetParameter(1, DBPARAMFLAGS_ISINPUT, NULL,
			DBTYPE_BYTES, reinterpret_cast<BYTE *>(vvbFmtFix[ieFix].Begin()),
			vvbFmtFix[ieFix].Size()));
		CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults));

		SetPercentComplete(50 + ((ieFix * 50) / ceFix));
	}
	SetPercentComplete(100);

	stuCmd.Assign(L"DROP TABLE #OwnedObjIdsTbl");
	CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults));

	// Fix style names in the view specs.
	// SQL gives us the power to just rename all items in one query. But that
	// won't correctly handle the situation of, for instance, renaming A to B, B to C, and
	// C to A. So we fix one item at a time and then update the database.

	static const GUID clsidNotebook =	// {39886581-4DD5-11D4-8078-0000C0FB81B5}
		{ 0x39886581, 0x4DD5, 0x11D4, { 0x80, 0x78, 0x00, 0x00, 0xC0, 0xFB, 0x81, 0xB5 } };
	static GUID clsidListEditor =	// {5EA62D01-7A78-11D4-8078-0000C0FB81B5}
		{ 0x5EA62D01, 0x7A78, 0x11D4, { 0x80, 0x78, 0x00, 0x00, 0xC0, 0xFB, 0x81, 0xB5 } };

	StrUni stuBaseCmd;
	stuBaseCmd.Format(L"%nFROM UserViewField a%n"
		L"JOIN UserViewRec_Fields uf on uf.Dst = a.Id%n"
		L"JOIN UserView_Records ur on ur.Dst = uf.Src%n"
		L"JOIN UserView uv on uv.Id = ur.Src%n"
		L"WHERE a.Style IS NOT NULL AND");
	if (*m_pclsidApp == clsidNotebook || *m_pclsidApp == clsidListEditor)
	{
		Assert(!fFixOwned);
		stuBaseCmd.FormatAppend(L"%n\tuv.App in ('%g','%g')", &clsidNotebook, &clsidListEditor);
	}
	else
	{
		Assert(fFixOwned);
		stuBaseCmd.FormatAppend(L" uv.App = '%g'", m_pclsidApp);
	}
	stuMsg.Load(kstidChgStylePhaseFour);
	m_qprog->put_Message(stuMsg.Bstr());
	SetPercentComplete(0);

	OLECHAR rgch[1024];
	vhobjFix.Clear();
	Vector<StrUni> vstuFix;

	stuCmdCnt.Format(L"SELECT COUNT(*)%s", stuBaseCmd.Chars());
	CheckHr(GetOleDbCommand()->ExecCommand(stuCmdCnt.Bstr(),
		knSqlStmtSelectWithOneRowset));
	CheckHr(GetOleDbCommand()->GetRowset(0));
	CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));
	CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&cRows),
		isizeof(hobj), &cbSpaceTaken, &fIsNull, 0));

	stuCmd.Format(L"SELECT a.Id, a.Style%s", stuBaseCmd.Chars());

	CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtSelectWithOneRowset));
	CheckHr(GetOleDbCommand()->GetRowset(0));
	CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));
	iRow = 0;
	while (fMoreRows)
	{
		CheckHr(GetOleDbCommand()->GetColValue(1, reinterpret_cast <BYTE *>(&hobj),
			isizeof(hobj), &cbSpaceTaken, &fIsNull, 0));
		CheckHr(GetOleDbCommand()->GetColValue(2, reinterpret_cast <BYTE *>(*&rgch),
			isizeof(rgch), &cbSpaceTaken, &fIsNull, 0));
		int cchw = cbSpaceTaken / isizeof(OLECHAR);

		StrUni stuOld(rgch, cchw);
		StrUni stuNew;
		if (Delete(stuOld))
		{
			vstuFix.Push(L"");
			vhobjFix.Push(hobj);
		}
		else if (Rename(stuOld, stuNew))
		{
			vstuFix.Push(stuNew);
			vhobjFix.Push(hobj);
		}

		CheckHr(GetOleDbCommand()->NextRow(&fMoreRows));

		iRow++;
		SetPercentComplete((iRow * 50) / cRows);
	}
	SetPercentComplete(50);
	Assert(vhobjFix.Size() == vstuFix.Size());

	ceFix = vstuFix.Size();
	for (int ieFix = 0; ieFix < ceFix; ieFix++)
	{
		if (vstuFix[ieFix] == L"")
		{
			stuCmd.Format(L"UPDATE UserViewField SET Style = NULL where [Id] = '%d'",
				vhobjFix[ieFix]);
		}
		else
		{
			StrUtil::NormalizeStrUni(vstuFix[ieFix], UNORM_NFD);
			stuCmd.Format(L"UPDATE UserViewField SET Style = '%s' where [Id] = '%d'",
				vstuFix[ieFix].Chars(), vhobjFix[ieFix]);
		}
		CheckHr(GetOleDbCommand()->ExecCommand(stuCmd.Bstr(), knSqlStmtNoResults));

		SetPercentComplete(50 + ((ieFix * 50) / ceFix));
	}

	SetPercentComplete(100);
	CommitTrans();
	Terminate(m_hvoRoot);

	if (m_qprog)
		m_qprog->DestroyHwnd();
	END_COM_METHOD(g_fact, IID_IFwDbMergeStyles);
}
Exemplo n.º 2
0
bool GdlRenderer::PreCompileFeatures(GrcManager * pcman, GrcFont * pfont, int * pfxdFeatVersion)
{
	*pfxdFeatVersion = 0x00010000;

	int nInternalID = 0;

	Set<unsigned int> setID;

	for (int ipfeat = 0; ipfeat < m_vpfeat.Size(); ipfeat++)
	{
		GdlFeatureDefn * pfeat = m_vpfeat[ipfeat];
		unsigned int nID = pfeat->ID();
		if (setID.IsMember(nID))
		{
			char rgch[20];
			if (nID > 0x00FFFFFF)
			{
				char rgchID[5];
				memcpy(rgch, &nID, 4);
				rgchID[0] = rgch[3]; rgchID[1] = rgch[2]; rgchID[2] = rgch[1]; rgchID[3] = rgch[0];
				rgchID[4] = 0;
				StrAnsi staTmp = "'";
				staTmp.Append(rgchID);
				staTmp.Append("'");
				memcpy(rgch, staTmp.Chars(), staTmp.Length() + 1);
			}
			else
				itoa(nID, rgch, 10);
			g_errorList.AddError(3152, pfeat, "Duplicate feature ID: ", rgch);
		}
		else
			setID.Insert(nID);

		if (pfeat->ErrorCheck())
		{
			pfeat->SetStdStyleFlag();
			pfeat->FillInBoolean(pcman->SymbolTable());
			pfeat->ErrorCheckContd();
			pfeat->CalculateDefault();
			pfeat->AssignInternalID(nInternalID);
			pfeat->RecordDebugInfo();
		}

		if (nID > 0x0000FFFF)
			*pfxdFeatVersion = 0x00020000;

		nInternalID++;
	}

	if (m_vpfeat.Size() > kMaxFeatures)
	{
		char rgchMax[20];
		itoa(kMaxFeatures, rgchMax, 10);
		char rgchCount[20];
		itoa(m_vpfeat.Size(), rgchCount, 10);
		g_errorList.AddError(3153, NULL,
			"Number of features (",
			rgchCount,
			") exceeds maximum of ",
			rgchMax);
	}

	return true;
}
Exemplo n.º 3
0
/*----------------------------------------------------------------------------------------------
	Write the given FontInfo string text property value in readable XML format.

	@param pstrm Pointer to an IStream for output.
	@param bstrVal
----------------------------------------------------------------------------------------------*/
void FwXml::WriteBulNumFontInfo(IStream * pstrm, BSTR bstrVal, int cchIndent)
{
	AssertPtr(pstrm);
	Assert(cchIndent >= 0);
	int cchProps = ::SysStringLen(bstrVal);
	if (!cchProps)
		return;

	Vector<char> vchIndent;
	vchIndent.Resize(cchIndent + 1);
	memset(vchIndent.Begin(), ' ', cchIndent);

	const OLECHAR * pchProps = bstrVal;
	const OLECHAR * pchPropsLim = pchProps + cchProps;

	FormatToStream(pstrm, "%s<BulNumFontInfo", vchIndent.Begin());
	StrAnsi staItalic;
	StrAnsi staBold;
	StrAnsi staSuperscript;
	StrAnsi staUnderline;
	StrAnsi staFontsize;
	StrAnsi staOffset;
	StrAnsi staForecolor;
	StrAnsi staBackcolor;
	StrAnsi staUndercolor;
	StrAnsi staXXX;
	int tpt;
	while (pchProps < pchPropsLim)
	{
		tpt = *pchProps++;
		if (tpt == ktptFontFamily)
			break;

		int nVal = *pchProps + ((*(pchProps + 1)) << 16);
		pchProps += 2;
		switch (tpt)
		{
		case ktptItalic:
			staItalic.Format(" italic=\"%s\"", ToggleValueName((byte)nVal));
			break;
		case ktptBold:
			staBold.Format(" bold=\"%s\"", ToggleValueName((byte)nVal));
			break;
		case ktptSuperscript:
			staSuperscript.Format(" superscript=\"%s\"", SuperscriptValName((byte)nVal));
			break;
		case ktptUnderline:
			staUnderline.Format(" underline=\"%s\"", UnderlineTypeName((byte)nVal));
			break;
		case ktptFontSize:
			staFontsize.Format(" fontsize=\"%dmpt\"", nVal);
			break;
		case ktptOffset:
			staOffset.Format(" offset=\"%dmpt\"", nVal);
			break;
		case ktptForeColor:
			staForecolor.Format(" forecolor=\"%s\"", ColorName(nVal));
			break;
		case ktptBackColor:
			staBackcolor.Format(" backcolor=\"%s\"", ColorName(nVal));
			break;
		case ktptUnderColor:
			staUndercolor.Format(" undercolor=\"%s\"", ColorName(nVal));
			break;
		default:
			staXXX.FormatAppend(" prop_%u=\"%d\"", tpt, nVal);
			break;
		}
	}
	ULONG cb;
	// Write the integer valued properties in alphabetical order.
	if (staBackcolor.Length())
		pstrm->Write(staBackcolor.Chars(), staBackcolor.Length(), &cb);
	if (staBold.Length())
		pstrm->Write(staBold.Chars(), staBold.Length(), &cb);
	if (staFontsize.Length())
		pstrm->Write(staFontsize.Chars(), staFontsize.Length(), &cb);
	if (staForecolor.Length())
		pstrm->Write(staForecolor.Chars(), staForecolor.Length(), &cb);
	if (staItalic.Length())
		pstrm->Write(staItalic.Chars(), staItalic.Length(), &cb);
	if (staOffset.Length())
		pstrm->Write(staOffset.Chars(), staOffset.Length(), &cb);
	if (staSuperscript.Length())
		pstrm->Write(staSuperscript.Chars(), staSuperscript.Length(), &cb);
	if (staUndercolor.Length())
		pstrm->Write(staUndercolor.Chars(), staUndercolor.Length(), &cb);
	if (staUnderline.Length())
		pstrm->Write(staUnderline.Chars(), staUnderline.Length(), &cb);
	if (staXXX.Length())
		pstrm->Write(staXXX.Chars(), staXXX.Length(), &cb);
	// Write the string valued property (if it exists).
	if (tpt == ktptFontFamily && pchProps < pchPropsLim)
	{
		FormatToStream(pstrm, " fontFamily=\"");
		WriteXmlUnicode(pstrm, pchProps, pchPropsLim - pchProps);
		FormatToStream(pstrm, "\"");
	}
	FormatToStream(pstrm, "/>%n");
}