Bug 570258: Some more atom usage cleanup. r=jst
[mozilla-central.git] / rdf / base / src / nsRDFXMLSerializer.cpp
blob159607e87984951c9bdf31f2027bf9799ba4d584
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=4 sw=4 et tw=80:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
17 * The Original Code is mozilla.org Code.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1999
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
25 * Chris Waterson <waterson@netscape.com>
26 * Axel Hecht <axel@pike.org>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either of the GNU General Public License Version 2 or later (the "GPL"),
30 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
42 #include "nsRDFXMLSerializer.h"
44 #include "nsIAtom.h"
45 #include "nsIOutputStream.h"
46 #include "nsIRDFService.h"
47 #include "nsIRDFContainerUtils.h"
48 #include "nsIServiceManager.h"
49 #include "nsString.h"
50 #include "nsXPIDLString.h"
51 #include "nsTArray.h"
52 #include "rdf.h"
53 #include "rdfutil.h"
55 #include "rdfIDataSource.h"
57 #include "nsITimelineService.h"
59 PRInt32 nsRDFXMLSerializer::gRefCnt = 0;
60 nsIRDFContainerUtils* nsRDFXMLSerializer::gRDFC;
61 nsIRDFResource* nsRDFXMLSerializer::kRDF_instanceOf;
62 nsIRDFResource* nsRDFXMLSerializer::kRDF_type;
63 nsIRDFResource* nsRDFXMLSerializer::kRDF_nextVal;
64 nsIRDFResource* nsRDFXMLSerializer::kRDF_Bag;
65 nsIRDFResource* nsRDFXMLSerializer::kRDF_Seq;
66 nsIRDFResource* nsRDFXMLSerializer::kRDF_Alt;
68 static const char kRDFDescriptionOpen[] = " <RDF:Description";
69 static const char kIDAttr[] = " RDF:ID=\"";
70 static const char kAboutAttr[] = " RDF:about=\"";
71 static const char kRDFDescriptionClose[] = " </RDF:Description>\n";
72 static const char kRDFResource1[] = " RDF:resource=\"";
73 static const char kRDFResource2[] = "\"/>\n";
74 static const char kRDFParseTypeInteger[] = " NC:parseType=\"Integer\">";
75 static const char kRDFParseTypeDate[] = " NC:parseType=\"Date\">";
76 static const char kRDFUnknown[] = "><!-- unknown node type -->";
78 NS_IMETHODIMP
79 nsRDFXMLSerializer::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
81 if (aOuter)
82 return NS_ERROR_NO_AGGREGATION;
84 nsCOMPtr<nsIRDFXMLSerializer> result = new nsRDFXMLSerializer();
85 if (! result)
86 return NS_ERROR_OUT_OF_MEMORY;
87 // The serializer object is here, addref gRefCnt so that the
88 // destructor can safely release it.
89 gRefCnt++;
91 nsresult rv;
92 rv = result->QueryInterface(aIID, aResult);
94 if (NS_FAILED(rv)) return rv;
96 if (gRefCnt == 1) do {
97 nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
98 if (NS_FAILED(rv)) break;
100 rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
101 &kRDF_instanceOf);
102 if (NS_FAILED(rv)) break;
104 rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"),
105 &kRDF_type);
106 if (NS_FAILED(rv)) break;
108 rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"),
109 &kRDF_nextVal);
110 if (NS_FAILED(rv)) break;
112 rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"),
113 &kRDF_Bag);
114 if (NS_FAILED(rv)) break;
116 rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"),
117 &kRDF_Seq);
118 if (NS_FAILED(rv)) break;
120 rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"),
121 &kRDF_Alt);
122 if (NS_FAILED(rv)) break;
124 rv = CallGetService("@mozilla.org/rdf/container-utils;1", &gRDFC);
125 if (NS_FAILED(rv)) break;
126 } while (0);
128 return rv;
131 nsRDFXMLSerializer::nsRDFXMLSerializer()
133 MOZ_COUNT_CTOR(nsRDFXMLSerializer);
136 nsRDFXMLSerializer::~nsRDFXMLSerializer()
138 MOZ_COUNT_DTOR(nsRDFXMLSerializer);
140 if (--gRefCnt == 0) {
141 NS_IF_RELEASE(kRDF_Bag);
142 NS_IF_RELEASE(kRDF_Seq);
143 NS_IF_RELEASE(kRDF_Alt);
144 NS_IF_RELEASE(kRDF_instanceOf);
145 NS_IF_RELEASE(kRDF_type);
146 NS_IF_RELEASE(kRDF_nextVal);
147 NS_IF_RELEASE(gRDFC);
151 NS_IMPL_ISUPPORTS2(nsRDFXMLSerializer, nsIRDFXMLSerializer, nsIRDFXMLSource)
153 NS_IMETHODIMP
154 nsRDFXMLSerializer::Init(nsIRDFDataSource* aDataSource)
156 if (! aDataSource)
157 return NS_ERROR_NULL_POINTER;
159 mDataSource = aDataSource;
160 mDataSource->GetURI(getter_Copies(mBaseURLSpec));
162 // Add the ``RDF'' prefix, by default.
163 nsCOMPtr<nsIAtom> prefix;
165 prefix = do_GetAtom("RDF");
166 AddNameSpace(prefix, NS_LITERAL_STRING("http://www.w3.org/1999/02/22-rdf-syntax-ns#"));
168 prefix = do_GetAtom("NC");
169 AddNameSpace(prefix, NS_LITERAL_STRING("http://home.netscape.com/NC-rdf#"));
171 mQNames.Init();
172 mPrefixID = 0;
174 return NS_OK;
177 NS_IMETHODIMP
178 nsRDFXMLSerializer::AddNameSpace(nsIAtom* aPrefix, const nsAString& aURI)
180 nsCOMPtr<nsIAtom> prefix = aPrefix;
181 if (!prefix) {
182 // Make up a prefix, we don't want default namespaces, so
183 // that we can use QNames for elements and attributes alike.
184 prefix = EnsureNewPrefix();
186 mNameSpaces.Put(aURI, prefix);
187 return NS_OK;
190 static nsresult
191 rdf_BlockingWrite(nsIOutputStream* stream, const char* buf, PRUint32 size)
193 PRUint32 written = 0;
194 PRUint32 remaining = size;
195 while (remaining > 0) {
196 nsresult rv;
197 PRUint32 cb;
199 if (NS_FAILED(rv = stream->Write(buf + written, remaining, &cb)))
200 return rv;
202 written += cb;
203 remaining -= cb;
205 return NS_OK;
208 static nsresult
209 rdf_BlockingWrite(nsIOutputStream* stream, const nsCSubstring& s)
211 return rdf_BlockingWrite(stream, s.BeginReading(), s.Length());
214 static nsresult
215 rdf_BlockingWrite(nsIOutputStream* stream, const nsAString& s)
217 NS_ConvertUTF16toUTF8 utf8(s);
218 return rdf_BlockingWrite(stream, utf8.get(), utf8.Length());
221 already_AddRefed<nsIAtom>
222 nsRDFXMLSerializer::EnsureNewPrefix()
224 nsAutoString qname;
225 nsCOMPtr<nsIAtom> prefix;
226 PRBool isNewPrefix;
227 do {
228 isNewPrefix = PR_TRUE;
229 qname.AssignLiteral("NS");
230 qname.AppendInt(++mPrefixID, 10);
231 prefix = do_GetAtom(qname);
232 nsNameSpaceMap::const_iterator iter = mNameSpaces.first();
233 while (iter != mNameSpaces.last() && isNewPrefix) {
234 isNewPrefix = (iter->mPrefix != prefix);
235 ++iter;
237 } while (!isNewPrefix);
238 nsIAtom* outPrefix = nsnull;
239 prefix.swap(outPrefix);
240 return outPrefix;
243 // This converts a property resource (like
244 // "http://www.w3.org/TR/WD-rdf-syntax#Description") into a QName
245 // ("RDF:Description"), and registers the namespace, if it's made up.
247 nsresult
248 nsRDFXMLSerializer::RegisterQName(nsIRDFResource* aResource)
250 nsCAutoString uri, qname;
251 aResource->GetValueUTF8(uri);
253 nsNameSpaceMap::const_iterator iter = mNameSpaces.GetNameSpaceOf(uri);
254 if (iter != mNameSpaces.last()) {
255 NS_ENSURE_TRUE(iter->mPrefix, NS_ERROR_UNEXPECTED);
256 iter->mPrefix->ToUTF8String(qname);
257 qname.Append(':');
258 qname += StringTail(uri, uri.Length() - iter->mURI.Length());
259 return mQNames.Put(aResource, qname) ? NS_OK : NS_ERROR_FAILURE;
262 // Okay, so we don't have it in our map. Try to make one up. This
263 // is very bogus.
264 PRInt32 i = uri.RFindChar('#'); // first try a '#'
265 if (i == -1) {
266 i = uri.RFindChar('/');
267 if (i == -1) {
268 // Okay, just punt and assume there is _no_ namespace on
269 // this thing...
270 return mQNames.Put(aResource, uri) ? NS_OK : NS_ERROR_FAILURE;
274 // Take whatever is to the right of the '#' or '/' and call it the
275 // local name, make up a prefix.
276 nsCOMPtr<nsIAtom> prefix = EnsureNewPrefix();
277 mNameSpaces.Put(StringHead(uri, i+1), prefix);
278 prefix->ToUTF8String(qname);
279 qname.Append(':');
280 qname += StringTail(uri, uri.Length() - (i + 1));
282 return mQNames.Put(aResource, qname) ? NS_OK : NS_ERROR_FAILURE;
285 nsresult
286 nsRDFXMLSerializer::GetQName(nsIRDFResource* aResource, nsCString& aQName)
288 return mQNames.Get(aResource, &aQName) ? NS_OK : NS_ERROR_UNEXPECTED;
291 PRBool
292 nsRDFXMLSerializer::IsContainerProperty(nsIRDFResource* aProperty)
294 // Return `true' if the property is an internal property related
295 // to being a container.
296 if (aProperty == kRDF_instanceOf)
297 return PR_TRUE;
299 if (aProperty == kRDF_nextVal)
300 return PR_TRUE;
302 PRBool isOrdinal = PR_FALSE;
303 gRDFC->IsOrdinalProperty(aProperty, &isOrdinal);
304 if (isOrdinal)
305 return PR_TRUE;
307 return PR_FALSE;
311 // convert '&', '<', and '>' into "&amp;", "&lt;", and "&gt", respectively.
312 static const char amp[] = "&amp;";
313 static const char lt[] = "&lt;";
314 static const char gt[] = "&gt;";
315 static const char quot[] = "&quot;";
317 static void
318 rdf_EscapeAmpersandsAndAngleBrackets(nsCString& s)
320 PRUint32 newLength, origLength;
321 newLength = origLength = s.Length();
323 // Compute the length of the result string.
324 const char* start = s.BeginReading();
325 const char* end = s.EndReading();
326 const char* c = start;
327 while (c != end) {
328 switch (*c) {
329 case '&' :
330 newLength += sizeof(amp) - 2;
331 break;
332 case '<':
333 case '>':
334 newLength += sizeof(gt) - 2;
335 break;
336 default:
337 break;
339 ++c;
341 if (newLength == origLength) {
342 // nothing to escape
343 return;
346 // escape the chars from the end back to the front.
347 s.SetLength(newLength);
349 // Buffer might have changed, get the pointers again
350 start = s.BeginReading(); // begin of string
351 c = start + origLength - 1; // last char in original string
352 char* w = s.EndWriting() - 1; // last char in grown buffer
353 while (c >= start) {
354 switch (*c) {
355 case '&' :
356 w -= 4;
357 nsCharTraits<char>::copy(w, amp, sizeof(amp) - 1);
358 break;
359 case '<':
360 w -= 3;
361 nsCharTraits<char>::copy(w, lt, sizeof(lt) - 1);
362 break;
363 case '>':
364 w -= 3;
365 nsCharTraits<char>::copy(w, gt, sizeof(gt) - 1);
366 break;
367 default:
368 *w = *c;
370 --w;
371 --c;
375 // convert '"' to "&quot;"
376 static void
377 rdf_EscapeQuotes(nsCString& s)
379 PRInt32 i = 0;
380 while ((i = s.FindChar('"', i)) != -1) {
381 s.Replace(i, 1, quot, sizeof(quot) - 1);
382 i += sizeof(quot) - 2;
386 static void
387 rdf_EscapeAttributeValue(nsCString& s)
389 rdf_EscapeAmpersandsAndAngleBrackets(s);
390 rdf_EscapeQuotes(s);
394 nsresult
395 nsRDFXMLSerializer::SerializeInlineAssertion(nsIOutputStream* aStream,
396 nsIRDFResource* aResource,
397 nsIRDFResource* aProperty,
398 nsIRDFLiteral* aValue)
400 nsresult rv;
401 nsCString qname;
402 rv = GetQName(aProperty, qname);
403 NS_ENSURE_SUCCESS(rv, rv);
405 rv = rdf_BlockingWrite(aStream,
406 NS_LITERAL_CSTRING("\n "));
407 if (NS_FAILED(rv)) return rv;
409 const PRUnichar* value;
410 aValue->GetValueConst(&value);
411 NS_ConvertUTF16toUTF8 s(value);
413 rdf_EscapeAttributeValue(s);
415 rv = rdf_BlockingWrite(aStream, qname);
416 if (NS_FAILED(rv)) return rv;
417 rv = rdf_BlockingWrite(aStream, "=\"", 2);
418 if (NS_FAILED(rv)) return rv;
419 s.Append('"');
420 return rdf_BlockingWrite(aStream, s);
423 nsresult
424 nsRDFXMLSerializer::SerializeChildAssertion(nsIOutputStream* aStream,
425 nsIRDFResource* aResource,
426 nsIRDFResource* aProperty,
427 nsIRDFNode* aValue)
429 nsCString qname;
430 nsresult rv = GetQName(aProperty, qname);
431 NS_ENSURE_SUCCESS(rv, rv);
433 rv = rdf_BlockingWrite(aStream, " <", 5);
434 if (NS_FAILED(rv)) return rv;
435 rv = rdf_BlockingWrite(aStream, qname);
436 if (NS_FAILED(rv)) return rv;
438 nsCOMPtr<nsIRDFResource> resource;
439 nsCOMPtr<nsIRDFLiteral> literal;
440 nsCOMPtr<nsIRDFInt> number;
441 nsCOMPtr<nsIRDFDate> date;
443 if ((resource = do_QueryInterface(aValue)) != nsnull) {
444 nsCAutoString uri;
445 resource->GetValueUTF8(uri);
447 rdf_MakeRelativeRef(mBaseURLSpec, uri);
448 rdf_EscapeAttributeValue(uri);
450 rv = rdf_BlockingWrite(aStream, kRDFResource1,
451 sizeof(kRDFResource1) - 1);
452 if (NS_FAILED(rv)) return rv;
453 rv = rdf_BlockingWrite(aStream, uri);
454 if (NS_FAILED(rv)) return rv;
455 rv = rdf_BlockingWrite(aStream, kRDFResource2,
456 sizeof(kRDFResource2) - 1);
457 if (NS_FAILED(rv)) return rv;
459 goto no_close_tag;
461 else if ((literal = do_QueryInterface(aValue)) != nsnull) {
462 const PRUnichar *value;
463 literal->GetValueConst(&value);
464 NS_ConvertUTF16toUTF8 s(value);
466 rdf_EscapeAmpersandsAndAngleBrackets(s);
468 rv = rdf_BlockingWrite(aStream, ">", 1);
469 if (NS_FAILED(rv)) return rv;
470 rv = rdf_BlockingWrite(aStream, s);
471 if (NS_FAILED(rv)) return rv;
473 else if ((number = do_QueryInterface(aValue)) != nsnull) {
474 PRInt32 value;
475 number->GetValue(&value);
477 nsCAutoString n;
478 n.AppendInt(value);
480 rv = rdf_BlockingWrite(aStream, kRDFParseTypeInteger,
481 sizeof(kRDFParseTypeInteger) - 1);
482 if (NS_FAILED(rv)) return rv;
483 rv = rdf_BlockingWrite(aStream, n);
484 if (NS_FAILED(rv)) return rv;
486 else if ((date = do_QueryInterface(aValue)) != nsnull) {
487 PRTime value;
488 date->GetValue(&value);
490 nsCAutoString s;
491 rdf_FormatDate(value, s);
493 rv = rdf_BlockingWrite(aStream, kRDFParseTypeDate,
494 sizeof(kRDFParseTypeDate) - 1);
495 if (NS_FAILED(rv)) return rv;
496 rv = rdf_BlockingWrite(aStream, s);
497 if (NS_FAILED(rv)) return rv;
499 else {
500 // XXX it doesn't support nsIRDFResource _or_ nsIRDFLiteral???
501 // We should serialize nsIRDFInt, nsIRDFDate, etc...
502 NS_WARNING("unknown RDF node type");
504 rv = rdf_BlockingWrite(aStream, kRDFUnknown, sizeof(kRDFUnknown) - 1);
505 if (NS_FAILED(rv)) return rv;
508 rv = rdf_BlockingWrite(aStream, "</", 2);
509 if (NS_FAILED(rv)) return rv;
510 rv = rdf_BlockingWrite(aStream, qname);
511 if (NS_FAILED(rv)) return rv;
512 return rdf_BlockingWrite(aStream, ">\n", 2);
514 no_close_tag:
515 return NS_OK;
518 nsresult
519 nsRDFXMLSerializer::SerializeProperty(nsIOutputStream* aStream,
520 nsIRDFResource* aResource,
521 nsIRDFResource* aProperty,
522 PRBool aInline,
523 PRInt32* aSkipped)
525 nsresult rv = NS_OK;
527 PRInt32 skipped = 0;
529 nsCOMPtr<nsISimpleEnumerator> assertions;
530 mDataSource->GetTargets(aResource, aProperty, PR_TRUE, getter_AddRefs(assertions));
531 if (! assertions)
532 return NS_ERROR_FAILURE;
534 // Serializing the assertion inline is ok as long as the property has
535 // only one target value, and it is a literal that doesn't include line
536 // breaks.
537 PRBool needsChild = PR_FALSE;
539 while (1) {
540 PRBool hasMore = PR_FALSE;
541 assertions->HasMoreElements(&hasMore);
542 if (! hasMore)
543 break;
545 nsCOMPtr<nsISupports> isupports;
546 assertions->GetNext(getter_AddRefs(isupports));
547 nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(isupports);
548 needsChild |= (!literal);
550 if (!needsChild) {
551 assertions->HasMoreElements(&needsChild);
552 if (!needsChild) {
553 const PRUnichar* literalVal = nsnull;
554 literal->GetValueConst(&literalVal);
555 if (literalVal) {
556 for (; *literalVal; literalVal++) {
557 if (*literalVal == PRUnichar('\n') ||
558 *literalVal == PRUnichar('\r')) {
559 needsChild = PR_TRUE;
560 break;
567 if (aInline && !needsChild) {
568 rv = SerializeInlineAssertion(aStream, aResource, aProperty, literal);
570 else if (!aInline && needsChild) {
571 nsCOMPtr<nsIRDFNode> value = do_QueryInterface(isupports);
572 rv = SerializeChildAssertion(aStream, aResource, aProperty, value);
574 else {
575 ++skipped;
576 rv = NS_OK;
579 if (NS_FAILED(rv))
580 break;
583 *aSkipped += skipped;
584 return rv;
588 nsresult
589 nsRDFXMLSerializer::SerializeDescription(nsIOutputStream* aStream,
590 nsIRDFResource* aResource)
592 nsresult rv;
594 PRBool isTypedNode = PR_FALSE;
595 nsCString typeQName;
597 nsCOMPtr<nsIRDFNode> typeNode;
598 mDataSource->GetTarget(aResource, kRDF_type, PR_TRUE, getter_AddRefs(typeNode));
599 if (typeNode) {
600 nsCOMPtr<nsIRDFResource> type = do_QueryInterface(typeNode, &rv);
601 if (type) {
602 // Try to get a namespace prefix. If none is available,
603 // just treat the description as if it weren't a typed node
604 // after all and emit rdf:type as a normal property. This
605 // seems preferable to using a bogus (invented) prefix.
606 isTypedNode = NS_SUCCEEDED(GetQName(type, typeQName));
610 nsCAutoString uri;
611 rv = aResource->GetValueUTF8(uri);
612 if (NS_FAILED(rv)) return rv;
614 rdf_MakeRelativeRef(mBaseURLSpec, uri);
615 rdf_EscapeAttributeValue(uri);
617 // Emit an open tag and the subject
618 if (isTypedNode) {
619 rv = rdf_BlockingWrite(aStream, NS_LITERAL_STRING(" <"));
620 if (NS_FAILED(rv)) return rv;
621 // Watch out for the default namespace!
622 rv = rdf_BlockingWrite(aStream, typeQName);
623 if (NS_FAILED(rv)) return rv;
625 else {
626 rv = rdf_BlockingWrite(aStream, kRDFDescriptionOpen,
627 sizeof(kRDFDescriptionOpen) - 1);
628 if (NS_FAILED(rv)) return rv;
630 if (uri[0] == PRUnichar('#')) {
631 uri.Cut(0, 1);
632 rv = rdf_BlockingWrite(aStream, kIDAttr, sizeof(kIDAttr) - 1);
634 else {
635 rv = rdf_BlockingWrite(aStream, kAboutAttr, sizeof(kAboutAttr) - 1);
637 if (NS_FAILED(rv)) return rv;
639 uri.Append('"');
640 rv = rdf_BlockingWrite(aStream, uri);
641 if (NS_FAILED(rv)) return rv;
643 // Any value that's a literal we can write out as an inline
644 // attribute on the RDF:Description
645 nsAutoTArray<nsIRDFResource*, 8> visited;
646 PRInt32 skipped = 0;
648 nsCOMPtr<nsISimpleEnumerator> arcs;
649 mDataSource->ArcLabelsOut(aResource, getter_AddRefs(arcs));
651 if (arcs) {
652 // Don't re-serialize rdf:type later on
653 if (isTypedNode)
654 visited.AppendElement(kRDF_type);
656 while (1) {
657 PRBool hasMore = PR_FALSE;
658 arcs->HasMoreElements(&hasMore);
659 if (! hasMore)
660 break;
662 nsCOMPtr<nsISupports> isupports;
663 arcs->GetNext(getter_AddRefs(isupports));
665 nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
666 if (! property)
667 continue;
669 // Ignore properties that pertain to containers; we may be
670 // called from SerializeContainer() if the container resource
671 // has been assigned non-container properties.
672 if (IsContainerProperty(property))
673 continue;
675 // Only serialize values for the property once.
676 if (visited.Contains(property.get()))
677 continue;
679 visited.AppendElement(property.get());
681 SerializeProperty(aStream, aResource, property, PR_TRUE, &skipped);
685 if (skipped) {
686 // Close the RDF:Description tag.
687 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(">\n"));
688 if (NS_FAILED(rv)) return rv;
690 // Now write out resources (which might have their own
691 // substructure) as children.
692 mDataSource->ArcLabelsOut(aResource, getter_AddRefs(arcs));
694 if (arcs) {
695 // Forget that we've visited anything
696 visited.Clear();
697 // ... except for rdf:type
698 if (isTypedNode)
699 visited.AppendElement(kRDF_type);
701 while (1) {
702 PRBool hasMore = PR_FALSE;
703 arcs->HasMoreElements(&hasMore);
704 if (! hasMore)
705 break;
707 nsCOMPtr<nsISupports> isupports;
708 arcs->GetNext(getter_AddRefs(isupports));
710 nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
711 if (! property)
712 continue;
714 // Ignore properties that pertain to containers; we may be
715 // called from SerializeContainer() if the container
716 // resource has been assigned non-container properties.
717 if (IsContainerProperty(property))
718 continue;
720 // have we already seen this property? If so, don't write it
721 // out again; serialize property will write each instance.
722 if (visited.Contains(property.get()))
723 continue;
725 visited.AppendElement(property.get());
727 SerializeProperty(aStream, aResource, property, PR_FALSE, &skipped);
731 // Emit a proper close-tag.
732 if (isTypedNode) {
733 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(" </"));
734 if (NS_FAILED(rv)) return rv;
735 // Watch out for the default namespace!
736 rdf_BlockingWrite(aStream, typeQName);
737 if (NS_FAILED(rv)) return rv;
738 rdf_BlockingWrite(aStream, ">\n", 2);
739 if (NS_FAILED(rv)) return rv;
741 else {
742 rv = rdf_BlockingWrite(aStream, kRDFDescriptionClose,
743 sizeof(kRDFDescriptionClose) - 1);
744 if (NS_FAILED(rv)) return rv;
747 else {
748 // If we saw _no_ child properties, then we can don't need a
749 // close-tag.
750 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(" />\n"));
751 if (NS_FAILED(rv)) return rv;
754 return NS_OK;
757 nsresult
758 nsRDFXMLSerializer::SerializeMember(nsIOutputStream* aStream,
759 nsIRDFResource* aContainer,
760 nsIRDFNode* aMember)
762 // If it's a resource, then output a "<RDF:li RDF:resource=... />"
763 // tag, because we'll be dumping the resource separately. (We
764 // iterate thru all the resources in the datasource,
765 // remember?) Otherwise, output the literal value.
767 nsCOMPtr<nsIRDFResource> resource;
768 nsCOMPtr<nsIRDFLiteral> literal;
769 nsCOMPtr<nsIRDFInt> number;
770 nsCOMPtr<nsIRDFDate> date;
772 static const char kRDFLIOpen[] = " <RDF:li";
773 nsresult rv = rdf_BlockingWrite(aStream, kRDFLIOpen,
774 sizeof(kRDFLIOpen) - 1);
775 if (NS_FAILED(rv)) return rv;
777 if ((resource = do_QueryInterface(aMember)) != nsnull) {
778 nsCAutoString uri;
779 resource->GetValueUTF8(uri);
781 rdf_MakeRelativeRef(mBaseURLSpec, uri);
782 rdf_EscapeAttributeValue(uri);
784 rv = rdf_BlockingWrite(aStream, kRDFResource1,
785 sizeof(kRDFResource1) - 1);
786 if (NS_FAILED(rv)) return rv;
787 rv = rdf_BlockingWrite(aStream, uri);
788 if (NS_FAILED(rv)) return rv;
789 rv = rdf_BlockingWrite(aStream, kRDFResource2,
790 sizeof(kRDFResource2) - 1);
791 if (NS_FAILED(rv)) return rv;
793 goto no_close_tag;
795 else if ((literal = do_QueryInterface(aMember)) != nsnull) {
796 const PRUnichar *value;
797 literal->GetValueConst(&value);
798 static const char kRDFLIOpenGT[] = ">";
799 // close the '<RDF:LI' before adding the literal
800 rv = rdf_BlockingWrite(aStream, kRDFLIOpenGT,
801 sizeof(kRDFLIOpenGT) - 1);
802 if (NS_FAILED(rv)) return rv;
804 NS_ConvertUTF16toUTF8 s(value);
805 rdf_EscapeAmpersandsAndAngleBrackets(s);
807 rv = rdf_BlockingWrite(aStream, s);
808 if (NS_FAILED(rv)) return rv;
810 else if ((number = do_QueryInterface(aMember)) != nsnull) {
811 PRInt32 value;
812 number->GetValue(&value);
814 nsCAutoString n;
815 n.AppendInt(value);
817 rv = rdf_BlockingWrite(aStream, kRDFParseTypeInteger,
818 sizeof(kRDFParseTypeInteger) - 1);
819 if (NS_FAILED(rv)) return rv;
820 rv = rdf_BlockingWrite(aStream, n);
821 if (NS_FAILED(rv)) return rv;
823 else if ((date = do_QueryInterface(aMember)) != nsnull) {
824 PRTime value;
825 date->GetValue(&value);
827 nsCAutoString s;
828 rdf_FormatDate(value, s);
830 rv = rdf_BlockingWrite(aStream, kRDFParseTypeDate,
831 sizeof(kRDFParseTypeDate) - 1);
832 if (NS_FAILED(rv)) return rv;
833 rv = rdf_BlockingWrite(aStream, s);
834 if (NS_FAILED(rv)) return rv;
836 else {
837 // XXX it doesn't support nsIRDFResource _or_ nsIRDFLiteral???
838 // We should serialize nsIRDFInt, nsIRDFDate, etc...
839 NS_WARNING("unknown RDF node type");
841 rv = rdf_BlockingWrite(aStream, kRDFUnknown, sizeof(kRDFUnknown) - 1);
842 if (NS_FAILED(rv)) return rv;
846 static const char kRDFLIClose[] = "</RDF:li>\n";
847 rv = rdf_BlockingWrite(aStream, kRDFLIClose, sizeof(kRDFLIClose) - 1);
848 if (NS_FAILED(rv)) return rv;
851 no_close_tag:
852 return NS_OK;
856 nsresult
857 nsRDFXMLSerializer::SerializeContainer(nsIOutputStream* aStream,
858 nsIRDFResource* aContainer)
860 nsresult rv;
861 nsCAutoString tag;
863 // Decide if it's a sequence, bag, or alternation, and print the
864 // appropriate tag-open sequence
866 if (IsA(mDataSource, aContainer, kRDF_Bag)) {
867 tag.AssignLiteral("RDF:Bag");
869 else if (IsA(mDataSource, aContainer, kRDF_Seq)) {
870 tag.AssignLiteral("RDF:Seq");
872 else if (IsA(mDataSource, aContainer, kRDF_Alt)) {
873 tag.AssignLiteral("RDF:Alt");
875 else {
876 NS_ASSERTION(PR_FALSE, "huh? this is _not_ a container.");
877 return NS_ERROR_UNEXPECTED;
880 rv = rdf_BlockingWrite(aStream, " <", 3);
881 if (NS_FAILED(rv)) return rv;
882 rv = rdf_BlockingWrite(aStream, tag);
883 if (NS_FAILED(rv)) return rv;
886 // Unfortunately, we always need to print out the identity of the
887 // resource, even if was constructed "anonymously". We need to do
888 // this because we never really know who else might be referring
889 // to it...
891 nsCAutoString uri;
892 if (NS_SUCCEEDED(aContainer->GetValueUTF8(uri))) {
893 rdf_MakeRelativeRef(mBaseURLSpec, uri);
895 rdf_EscapeAttributeValue(uri);
897 if (uri.First() == '#') {
898 // Okay, it's actually identified as an element in the
899 // current document, not trying to decorate some absolute
900 // URI. We can use the 'ID=' attribute...
902 uri.Cut(0, 1); // chop the '#'
903 rv = rdf_BlockingWrite(aStream, kIDAttr, sizeof(kIDAttr) - 1);
904 if (NS_FAILED(rv)) return rv;
906 else {
907 // We need to cheat and spit out an illegal 'about=' on
908 // the sequence.
909 rv = rdf_BlockingWrite(aStream, kAboutAttr,
910 sizeof(kAboutAttr) - 1);
911 if (NS_FAILED(rv)) return rv;
914 rv = rdf_BlockingWrite(aStream, uri);
915 if (NS_FAILED(rv)) return rv;
916 rv = rdf_BlockingWrite(aStream, "\"", 1);
917 if (NS_FAILED(rv)) return rv;
920 rv = rdf_BlockingWrite(aStream, ">\n", 2);
921 if (NS_FAILED(rv)) return rv;
923 // First iterate through each of the ordinal elements (the RDF/XML
924 // syntax doesn't allow us to place properties on RDF container
925 // elements).
926 nsCOMPtr<nsISimpleEnumerator> elements;
927 rv = NS_NewContainerEnumerator(mDataSource, aContainer, getter_AddRefs(elements));
929 if (NS_SUCCEEDED(rv)) {
930 while (1) {
931 PRBool hasMore;
932 rv = elements->HasMoreElements(&hasMore);
933 if (NS_FAILED(rv)) break;
935 if (! hasMore)
936 break;
938 nsCOMPtr<nsISupports> isupports;
939 elements->GetNext(getter_AddRefs(isupports));
941 nsCOMPtr<nsIRDFNode> element = do_QueryInterface(isupports);
942 NS_ASSERTION(element != nsnull, "not an nsIRDFNode");
943 if (! element)
944 continue;
946 SerializeMember(aStream, aContainer, element);
950 // close the container tag
951 rv = rdf_BlockingWrite(aStream, " </", 4);
952 if (NS_FAILED(rv)) return rv;
953 tag.Append(">\n", 2);
954 rv = rdf_BlockingWrite(aStream, tag);
955 if (NS_FAILED(rv)) return rv;
957 // Now, we iterate through _all_ of the arcs, in case someone has
958 // applied properties to the bag itself. These'll be placed in a
959 // separate RDF:Description element.
960 nsCOMPtr<nsISimpleEnumerator> arcs;
961 mDataSource->ArcLabelsOut(aContainer, getter_AddRefs(arcs));
963 PRBool wroteDescription = PR_FALSE;
964 while (! wroteDescription) {
965 PRBool hasMore = PR_FALSE;
966 rv = arcs->HasMoreElements(&hasMore);
967 if (NS_FAILED(rv)) break;
969 if (! hasMore)
970 break;
972 nsIRDFResource* property;
973 rv = arcs->GetNext((nsISupports**) &property);
974 if (NS_FAILED(rv)) break;
976 // If it's a membership property, then output a "LI"
977 // tag. Otherwise, output a property.
978 if (! IsContainerProperty(property)) {
979 rv = SerializeDescription(aStream, aContainer);
980 wroteDescription = PR_TRUE;
983 NS_RELEASE(property);
984 if (NS_FAILED(rv))
985 break;
988 return NS_OK;
992 nsresult
993 nsRDFXMLSerializer::SerializePrologue(nsIOutputStream* aStream)
995 static const char kXMLVersion[] = "<?xml version=\"1.0\"?>\n";
997 nsresult rv;
998 rv = rdf_BlockingWrite(aStream, kXMLVersion, sizeof(kXMLVersion) - 1);
999 if (NS_FAILED(rv)) return rv;
1001 // global name space declarations
1002 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("<RDF:RDF "));
1003 if (NS_FAILED(rv)) return rv;
1005 nsNameSpaceMap::const_iterator first = mNameSpaces.first();
1006 nsNameSpaceMap::const_iterator last = mNameSpaces.last();
1007 for (nsNameSpaceMap::const_iterator entry = first; entry != last; ++entry) {
1008 if (entry != first) {
1009 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("\n "));
1010 if (NS_FAILED(rv)) return rv;
1012 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("xmlns"));
1013 if (NS_FAILED(rv)) return rv;
1015 if (entry->mPrefix) {
1016 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(":"));
1017 if (NS_FAILED(rv)) return rv;
1018 nsCAutoString prefix;
1019 entry->mPrefix->ToUTF8String(prefix);
1020 rv = rdf_BlockingWrite(aStream, prefix);
1021 if (NS_FAILED(rv)) return rv;
1024 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("=\""));
1025 if (NS_FAILED(rv)) return rv;
1026 nsCAutoString uri(entry->mURI);
1027 rdf_EscapeAttributeValue(uri);
1028 rv = rdf_BlockingWrite(aStream, uri);
1029 if (NS_FAILED(rv)) return rv;
1030 rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("\""));
1031 if (NS_FAILED(rv)) return rv;
1034 return rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(">\n"));
1038 nsresult
1039 nsRDFXMLSerializer::SerializeEpilogue(nsIOutputStream* aStream)
1041 return rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("</RDF:RDF>\n"));
1044 class QNameCollector : public rdfITripleVisitor {
1045 public:
1046 NS_DECL_ISUPPORTS
1047 NS_DECL_RDFITRIPLEVISITOR
1048 QNameCollector(nsRDFXMLSerializer* aParent)
1049 : mParent(aParent){}
1050 private:
1051 nsRDFXMLSerializer* mParent;
1054 NS_IMPL_ISUPPORTS1(QNameCollector, rdfITripleVisitor)
1055 nsresult
1056 QNameCollector::Visit(nsIRDFNode* aSubject, nsIRDFResource* aPredicate,
1057 nsIRDFNode* aObject, PRBool aTruthValue)
1059 if (aPredicate == mParent->kRDF_type) {
1060 // try to get a type QName for aObject, should be a resource
1061 nsCOMPtr<nsIRDFResource> resType = do_QueryInterface(aObject);
1062 if (!resType) {
1063 // ignore error
1064 return NS_OK;
1066 if (mParent->mQNames.Get(resType, nsnull)) {
1067 return NS_OK;
1069 mParent->RegisterQName(resType);
1070 return NS_OK;
1073 if (mParent->mQNames.Get(aPredicate, nsnull)) {
1074 return NS_OK;
1076 if (aPredicate == mParent->kRDF_instanceOf ||
1077 aPredicate == mParent->kRDF_nextVal)
1078 return NS_OK;
1079 PRBool isOrdinal = PR_FALSE;
1080 mParent->gRDFC->IsOrdinalProperty(aPredicate, &isOrdinal);
1081 if (isOrdinal)
1082 return NS_OK;
1084 mParent->RegisterQName(aPredicate);
1086 return NS_OK;
1089 nsresult
1090 nsRDFXMLSerializer::CollectNamespaces()
1092 // Iterate over all Triples to get namespaces for subject resource types
1093 // and Predicates and cache all the QNames we want to use.
1094 nsCOMPtr<rdfITripleVisitor> collector =
1095 new QNameCollector(this);
1096 nsCOMPtr<rdfIDataSource> ds = do_QueryInterface(mDataSource); // XXX API
1097 NS_ENSURE_TRUE(collector && ds, NS_ERROR_FAILURE);
1098 return ds->VisitAllTriples(collector);
1101 //----------------------------------------------------------------------
1103 NS_IMETHODIMP
1104 nsRDFXMLSerializer::Serialize(nsIOutputStream* aStream)
1106 nsresult rv;
1107 NS_TIMELINE_START_TIMER("rdf/xml-ser");
1109 rv = CollectNamespaces();
1110 if (NS_FAILED(rv)) return rv;
1112 nsCOMPtr<nsISimpleEnumerator> resources;
1113 rv = mDataSource->GetAllResources(getter_AddRefs(resources));
1114 if (NS_FAILED(rv)) return rv;
1116 rv = SerializePrologue(aStream);
1117 if (NS_FAILED(rv))
1118 return rv;
1120 while (1) {
1121 PRBool hasMore = PR_FALSE;
1122 resources->HasMoreElements(&hasMore);
1123 if (! hasMore)
1124 break;
1126 nsCOMPtr<nsISupports> isupports;
1127 resources->GetNext(getter_AddRefs(isupports));
1129 nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports);
1130 if (! resource)
1131 continue;
1133 if (IsA(mDataSource, resource, kRDF_Bag) ||
1134 IsA(mDataSource, resource, kRDF_Seq) ||
1135 IsA(mDataSource, resource, kRDF_Alt)) {
1136 rv = SerializeContainer(aStream, resource);
1138 else {
1139 rv = SerializeDescription(aStream, resource);
1142 if (NS_FAILED(rv))
1143 break;
1146 rv = SerializeEpilogue(aStream);
1147 NS_TIMELINE_STOP_TIMER("rdf/xml-ser");
1148 NS_TIMELINE_MARK("rdf/xml-ser");
1150 return rv;
1154 PRBool
1155 nsRDFXMLSerializer::IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType)
1157 nsresult rv;
1159 PRBool result;
1160 rv = aDataSource->HasAssertion(aResource, kRDF_instanceOf, aType, PR_TRUE, &result);
1161 if (NS_FAILED(rv)) return PR_FALSE;
1163 return result;