diff options
Diffstat (limited to 'rdf/base/nsRDFXMLSerializer.cpp')
-rw-r--r-- | rdf/base/nsRDFXMLSerializer.cpp | 1127 |
1 files changed, 1127 insertions, 0 deletions
diff --git a/rdf/base/nsRDFXMLSerializer.cpp b/rdf/base/nsRDFXMLSerializer.cpp new file mode 100644 index 000000000..677f58ca3 --- /dev/null +++ b/rdf/base/nsRDFXMLSerializer.cpp @@ -0,0 +1,1127 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=80: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsRDFXMLSerializer.h" + +#include "nsIAtom.h" +#include "nsIOutputStream.h" +#include "nsIRDFService.h" +#include "nsIRDFContainerUtils.h" +#include "nsIServiceManager.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsTArray.h" +#include "rdf.h" +#include "rdfutil.h" +#include "mozilla/Attributes.h" + +#include "rdfIDataSource.h" + +int32_t nsRDFXMLSerializer::gRefCnt = 0; +nsIRDFContainerUtils* nsRDFXMLSerializer::gRDFC; +nsIRDFResource* nsRDFXMLSerializer::kRDF_instanceOf; +nsIRDFResource* nsRDFXMLSerializer::kRDF_type; +nsIRDFResource* nsRDFXMLSerializer::kRDF_nextVal; +nsIRDFResource* nsRDFXMLSerializer::kRDF_Bag; +nsIRDFResource* nsRDFXMLSerializer::kRDF_Seq; +nsIRDFResource* nsRDFXMLSerializer::kRDF_Alt; + +static const char kRDFDescriptionOpen[] = " <RDF:Description"; +static const char kIDAttr[] = " RDF:ID=\""; +static const char kAboutAttr[] = " RDF:about=\""; +static const char kRDFDescriptionClose[] = " </RDF:Description>\n"; +static const char kRDFResource1[] = " RDF:resource=\""; +static const char kRDFResource2[] = "\"/>\n"; +static const char kRDFParseTypeInteger[] = " NC:parseType=\"Integer\">"; +static const char kRDFParseTypeDate[] = " NC:parseType=\"Date\">"; +static const char kRDFUnknown[] = "><!-- unknown node type -->"; + +nsresult +nsRDFXMLSerializer::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsCOMPtr<nsIRDFXMLSerializer> result = new nsRDFXMLSerializer(); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + // The serializer object is here, addref gRefCnt so that the + // destructor can safely release it. + gRefCnt++; + + nsresult rv; + rv = result->QueryInterface(aIID, aResult); + + if (NS_FAILED(rv)) return rv; + + if (gRefCnt == 1) do { + nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"), + &kRDF_instanceOf); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"), + &kRDF_type); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"), + &kRDF_nextVal); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"), + &kRDF_Bag); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"), + &kRDF_Seq); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"), + &kRDF_Alt); + if (NS_FAILED(rv)) break; + + rv = CallGetService("@mozilla.org/rdf/container-utils;1", &gRDFC); + if (NS_FAILED(rv)) break; + } while (0); + + return rv; +} + +nsRDFXMLSerializer::nsRDFXMLSerializer() +{ + MOZ_COUNT_CTOR(nsRDFXMLSerializer); +} + +nsRDFXMLSerializer::~nsRDFXMLSerializer() +{ + MOZ_COUNT_DTOR(nsRDFXMLSerializer); + + if (--gRefCnt == 0) { + NS_IF_RELEASE(kRDF_Bag); + NS_IF_RELEASE(kRDF_Seq); + NS_IF_RELEASE(kRDF_Alt); + NS_IF_RELEASE(kRDF_instanceOf); + NS_IF_RELEASE(kRDF_type); + NS_IF_RELEASE(kRDF_nextVal); + NS_IF_RELEASE(gRDFC); + } +} + +NS_IMPL_ISUPPORTS(nsRDFXMLSerializer, nsIRDFXMLSerializer, nsIRDFXMLSource) + +NS_IMETHODIMP +nsRDFXMLSerializer::Init(nsIRDFDataSource* aDataSource) +{ + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + mDataSource = aDataSource; + mDataSource->GetURI(getter_Copies(mBaseURLSpec)); + + // Add the ``RDF'' prefix, by default. + nsCOMPtr<nsIAtom> prefix; + + prefix = NS_Atomize("RDF"); + AddNameSpace(prefix, NS_LITERAL_STRING("http://www.w3.org/1999/02/22-rdf-syntax-ns#")); + + prefix = NS_Atomize("NC"); + AddNameSpace(prefix, NS_LITERAL_STRING("http://home.netscape.com/NC-rdf#")); + + mPrefixID = 0; + + return NS_OK; +} + +NS_IMETHODIMP +nsRDFXMLSerializer::AddNameSpace(nsIAtom* aPrefix, const nsAString& aURI) +{ + nsCOMPtr<nsIAtom> prefix = aPrefix; + if (!prefix) { + // Make up a prefix, we don't want default namespaces, so + // that we can use QNames for elements and attributes alike. + prefix = EnsureNewPrefix(); + } + mNameSpaces.Put(aURI, prefix); + return NS_OK; +} + +static nsresult +rdf_BlockingWrite(nsIOutputStream* stream, const char* buf, uint32_t size) +{ + uint32_t written = 0; + uint32_t remaining = size; + while (remaining > 0) { + nsresult rv; + uint32_t cb; + + if (NS_FAILED(rv = stream->Write(buf + written, remaining, &cb))) + return rv; + + written += cb; + remaining -= cb; + } + return NS_OK; +} + +static nsresult +rdf_BlockingWrite(nsIOutputStream* stream, const nsCSubstring& s) +{ + return rdf_BlockingWrite(stream, s.BeginReading(), s.Length()); +} + +static nsresult +rdf_BlockingWrite(nsIOutputStream* stream, const nsAString& s) +{ + NS_ConvertUTF16toUTF8 utf8(s); + return rdf_BlockingWrite(stream, utf8.get(), utf8.Length()); +} + +already_AddRefed<nsIAtom> +nsRDFXMLSerializer::EnsureNewPrefix() +{ + nsAutoString qname; + nsCOMPtr<nsIAtom> prefix; + bool isNewPrefix; + do { + isNewPrefix = true; + qname.AssignLiteral("NS"); + qname.AppendInt(++mPrefixID, 10); + prefix = NS_Atomize(qname); + nsNameSpaceMap::const_iterator iter = mNameSpaces.first(); + while (iter != mNameSpaces.last() && isNewPrefix) { + isNewPrefix = (iter->mPrefix != prefix); + ++iter; + } + } while (!isNewPrefix); + return prefix.forget(); +} + +// This converts a property resource (like +// "http://www.w3.org/TR/WD-rdf-syntax#Description") into a QName +// ("RDF:Description"), and registers the namespace, if it's made up. + +nsresult +nsRDFXMLSerializer::RegisterQName(nsIRDFResource* aResource) +{ + nsAutoCString uri, qname; + aResource->GetValueUTF8(uri); + + nsNameSpaceMap::const_iterator iter = mNameSpaces.GetNameSpaceOf(uri); + if (iter != mNameSpaces.last()) { + NS_ENSURE_TRUE(iter->mPrefix, NS_ERROR_UNEXPECTED); + iter->mPrefix->ToUTF8String(qname); + qname.Append(':'); + qname += StringTail(uri, uri.Length() - iter->mURI.Length()); + mQNames.Put(aResource, qname); + return NS_OK; + } + + // Okay, so we don't have it in our map. Try to make one up. This + // is very bogus. + int32_t i = uri.RFindChar('#'); // first try a '#' + if (i == -1) { + i = uri.RFindChar('/'); + if (i == -1) { + // Okay, just punt and assume there is _no_ namespace on + // this thing... + mQNames.Put(aResource, uri); + return NS_OK; + } + } + + // Take whatever is to the right of the '#' or '/' and call it the + // local name, make up a prefix. + nsCOMPtr<nsIAtom> prefix = EnsureNewPrefix(); + mNameSpaces.Put(StringHead(uri, i+1), prefix); + prefix->ToUTF8String(qname); + qname.Append(':'); + qname += StringTail(uri, uri.Length() - (i + 1)); + + mQNames.Put(aResource, qname); + return NS_OK; +} + +nsresult +nsRDFXMLSerializer::GetQName(nsIRDFResource* aResource, nsCString& aQName) +{ + return mQNames.Get(aResource, &aQName) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +bool +nsRDFXMLSerializer::IsContainerProperty(nsIRDFResource* aProperty) +{ + // Return `true' if the property is an internal property related + // to being a container. + if (aProperty == kRDF_instanceOf) + return true; + + if (aProperty == kRDF_nextVal) + return true; + + bool isOrdinal = false; + gRDFC->IsOrdinalProperty(aProperty, &isOrdinal); + if (isOrdinal) + return true; + + return false; +} + + +// convert '&', '<', and '>' into "&", "<", and ">", respectively. +static const char amp[] = "&"; +static const char lt[] = "<"; +static const char gt[] = ">"; +static const char quot[] = """; + +static void +rdf_EscapeAmpersandsAndAngleBrackets(nsCString& s) +{ + uint32_t newLength, origLength; + newLength = origLength = s.Length(); + + // Compute the length of the result string. + const char* start = s.BeginReading(); + const char* end = s.EndReading(); + const char* c = start; + while (c != end) { + switch (*c) { + case '&' : + newLength += sizeof(amp) - 2; + break; + case '<': + case '>': + newLength += sizeof(gt) - 2; + break; + default: + break; + } + ++c; + } + if (newLength == origLength) { + // nothing to escape + return; + } + + // escape the chars from the end back to the front. + s.SetLength(newLength); + + // Buffer might have changed, get the pointers again + start = s.BeginReading(); // begin of string + c = start + origLength - 1; // last char in original string + char* w = s.EndWriting() - 1; // last char in grown buffer + while (c >= start) { + switch (*c) { + case '&' : + w -= 4; + nsCharTraits<char>::copy(w, amp, sizeof(amp) - 1); + break; + case '<': + w -= 3; + nsCharTraits<char>::copy(w, lt, sizeof(lt) - 1); + break; + case '>': + w -= 3; + nsCharTraits<char>::copy(w, gt, sizeof(gt) - 1); + break; + default: + *w = *c; + } + --w; + --c; + } +} + +// convert '"' to """ +static void +rdf_EscapeQuotes(nsCString& s) +{ + int32_t i = 0; + while ((i = s.FindChar('"', i)) != -1) { + s.Replace(i, 1, quot, sizeof(quot) - 1); + i += sizeof(quot) - 2; + } +} + +static void +rdf_EscapeAttributeValue(nsCString& s) +{ + rdf_EscapeAmpersandsAndAngleBrackets(s); + rdf_EscapeQuotes(s); +} + + +nsresult +nsRDFXMLSerializer::SerializeInlineAssertion(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + nsIRDFLiteral* aValue) +{ + nsresult rv; + nsCString qname; + rv = GetQName(aProperty, qname); + NS_ENSURE_SUCCESS(rv, rv); + + rv = rdf_BlockingWrite(aStream, + NS_LITERAL_CSTRING("\n ")); + if (NS_FAILED(rv)) return rv; + + const char16_t* value; + aValue->GetValueConst(&value); + NS_ConvertUTF16toUTF8 s(value); + + rdf_EscapeAttributeValue(s); + + rv = rdf_BlockingWrite(aStream, qname); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, "=\"", 2); + if (NS_FAILED(rv)) return rv; + s.Append('"'); + return rdf_BlockingWrite(aStream, s); +} + +nsresult +nsRDFXMLSerializer::SerializeChildAssertion(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue) +{ + nsCString qname; + nsresult rv = GetQName(aProperty, qname); + NS_ENSURE_SUCCESS(rv, rv); + + rv = rdf_BlockingWrite(aStream, " <", 5); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, qname); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIRDFResource> resource; + nsCOMPtr<nsIRDFLiteral> literal; + nsCOMPtr<nsIRDFInt> number; + nsCOMPtr<nsIRDFDate> date; + + if ((resource = do_QueryInterface(aValue)) != nullptr) { + nsAutoCString uri; + resource->GetValueUTF8(uri); + + rdf_MakeRelativeRef(mBaseURLSpec, uri); + rdf_EscapeAttributeValue(uri); + + rv = rdf_BlockingWrite(aStream, kRDFResource1, + sizeof(kRDFResource1) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, kRDFResource2, + sizeof(kRDFResource2) - 1); + if (NS_FAILED(rv)) return rv; + + goto no_close_tag; + } + else if ((literal = do_QueryInterface(aValue)) != nullptr) { + const char16_t *value; + literal->GetValueConst(&value); + NS_ConvertUTF16toUTF8 s(value); + + rdf_EscapeAmpersandsAndAngleBrackets(s); + + rv = rdf_BlockingWrite(aStream, ">", 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, s); + if (NS_FAILED(rv)) return rv; + } + else if ((number = do_QueryInterface(aValue)) != nullptr) { + int32_t value; + number->GetValue(&value); + + nsAutoCString n; + n.AppendInt(value); + + rv = rdf_BlockingWrite(aStream, kRDFParseTypeInteger, + sizeof(kRDFParseTypeInteger) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, n); + if (NS_FAILED(rv)) return rv; + } + else if ((date = do_QueryInterface(aValue)) != nullptr) { + PRTime value; + date->GetValue(&value); + + nsAutoCString s; + rdf_FormatDate(value, s); + + rv = rdf_BlockingWrite(aStream, kRDFParseTypeDate, + sizeof(kRDFParseTypeDate) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, s); + if (NS_FAILED(rv)) return rv; + } + else { + // XXX it doesn't support nsIRDFResource _or_ nsIRDFLiteral??? + // We should serialize nsIRDFInt, nsIRDFDate, etc... + NS_WARNING("unknown RDF node type"); + + rv = rdf_BlockingWrite(aStream, kRDFUnknown, sizeof(kRDFUnknown) - 1); + if (NS_FAILED(rv)) return rv; + } + + rv = rdf_BlockingWrite(aStream, "</", 2); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, qname); + if (NS_FAILED(rv)) return rv; + return rdf_BlockingWrite(aStream, ">\n", 2); + + no_close_tag: + return NS_OK; +} + +nsresult +nsRDFXMLSerializer::SerializeProperty(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + bool aInline, + int32_t* aSkipped) +{ + nsresult rv = NS_OK; + + int32_t skipped = 0; + + nsCOMPtr<nsISimpleEnumerator> assertions; + mDataSource->GetTargets(aResource, aProperty, true, getter_AddRefs(assertions)); + if (! assertions) + return NS_ERROR_FAILURE; + + // Serializing the assertion inline is ok as long as the property has + // only one target value, and it is a literal that doesn't include line + // breaks. + bool needsChild = false; + + while (1) { + bool hasMore = false; + assertions->HasMoreElements(&hasMore); + if (! hasMore) + break; + + nsCOMPtr<nsISupports> isupports; + assertions->GetNext(getter_AddRefs(isupports)); + nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(isupports); + needsChild |= (!literal); + + if (!needsChild) { + assertions->HasMoreElements(&needsChild); + if (!needsChild) { + const char16_t* literalVal = nullptr; + literal->GetValueConst(&literalVal); + if (literalVal) { + for (; *literalVal; literalVal++) { + if (*literalVal == char16_t('\n') || + *literalVal == char16_t('\r')) { + needsChild = true; + break; + } + } + } + } + } + + if (aInline && !needsChild) { + rv = SerializeInlineAssertion(aStream, aResource, aProperty, literal); + } + else if (!aInline && needsChild) { + nsCOMPtr<nsIRDFNode> value = do_QueryInterface(isupports); + rv = SerializeChildAssertion(aStream, aResource, aProperty, value); + } + else { + ++skipped; + rv = NS_OK; + } + + if (NS_FAILED(rv)) + break; + } + + *aSkipped += skipped; + return rv; +} + + +nsresult +nsRDFXMLSerializer::SerializeDescription(nsIOutputStream* aStream, + nsIRDFResource* aResource) +{ + nsresult rv; + + bool isTypedNode = false; + nsCString typeQName; + + nsCOMPtr<nsIRDFNode> typeNode; + mDataSource->GetTarget(aResource, kRDF_type, true, getter_AddRefs(typeNode)); + if (typeNode) { + nsCOMPtr<nsIRDFResource> type = do_QueryInterface(typeNode, &rv); + if (type) { + // Try to get a namespace prefix. If none is available, + // just treat the description as if it weren't a typed node + // after all and emit rdf:type as a normal property. This + // seems preferable to using a bogus (invented) prefix. + isTypedNode = NS_SUCCEEDED(GetQName(type, typeQName)); + } + } + + nsAutoCString uri; + rv = aResource->GetValueUTF8(uri); + if (NS_FAILED(rv)) return rv; + + rdf_MakeRelativeRef(mBaseURLSpec, uri); + rdf_EscapeAttributeValue(uri); + + // Emit an open tag and the subject + if (isTypedNode) { + rv = rdf_BlockingWrite(aStream, NS_LITERAL_STRING(" <")); + if (NS_FAILED(rv)) return rv; + // Watch out for the default namespace! + rv = rdf_BlockingWrite(aStream, typeQName); + if (NS_FAILED(rv)) return rv; + } + else { + rv = rdf_BlockingWrite(aStream, kRDFDescriptionOpen, + sizeof(kRDFDescriptionOpen) - 1); + if (NS_FAILED(rv)) return rv; + } + if (uri[0] == char16_t('#')) { + uri.Cut(0, 1); + rv = rdf_BlockingWrite(aStream, kIDAttr, sizeof(kIDAttr) - 1); + } + else { + rv = rdf_BlockingWrite(aStream, kAboutAttr, sizeof(kAboutAttr) - 1); + } + if (NS_FAILED(rv)) return rv; + + uri.Append('"'); + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + + // Any value that's a literal we can write out as an inline + // attribute on the RDF:Description + AutoTArray<nsIRDFResource*, 8> visited; + int32_t skipped = 0; + + nsCOMPtr<nsISimpleEnumerator> arcs; + mDataSource->ArcLabelsOut(aResource, getter_AddRefs(arcs)); + + if (arcs) { + // Don't re-serialize rdf:type later on + if (isTypedNode) + visited.AppendElement(kRDF_type); + + while (1) { + bool hasMore = false; + arcs->HasMoreElements(&hasMore); + if (! hasMore) + break; + + nsCOMPtr<nsISupports> isupports; + arcs->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports); + if (! property) + continue; + + // Ignore properties that pertain to containers; we may be + // called from SerializeContainer() if the container resource + // has been assigned non-container properties. + if (IsContainerProperty(property)) + continue; + + // Only serialize values for the property once. + if (visited.Contains(property.get())) + continue; + + visited.AppendElement(property.get()); + + SerializeProperty(aStream, aResource, property, true, &skipped); + } + } + + if (skipped) { + // Close the RDF:Description tag. + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(">\n")); + if (NS_FAILED(rv)) return rv; + + // Now write out resources (which might have their own + // substructure) as children. + mDataSource->ArcLabelsOut(aResource, getter_AddRefs(arcs)); + + if (arcs) { + // Forget that we've visited anything + visited.Clear(); + // ... except for rdf:type + if (isTypedNode) + visited.AppendElement(kRDF_type); + + while (1) { + bool hasMore = false; + arcs->HasMoreElements(&hasMore); + if (! hasMore) + break; + + nsCOMPtr<nsISupports> isupports; + arcs->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports); + if (! property) + continue; + + // Ignore properties that pertain to containers; we may be + // called from SerializeContainer() if the container + // resource has been assigned non-container properties. + if (IsContainerProperty(property)) + continue; + + // have we already seen this property? If so, don't write it + // out again; serialize property will write each instance. + if (visited.Contains(property.get())) + continue; + + visited.AppendElement(property.get()); + + SerializeProperty(aStream, aResource, property, false, &skipped); + } + } + + // Emit a proper close-tag. + if (isTypedNode) { + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(" </")); + if (NS_FAILED(rv)) return rv; + // Watch out for the default namespace! + rdf_BlockingWrite(aStream, typeQName); + if (NS_FAILED(rv)) return rv; + rdf_BlockingWrite(aStream, ">\n", 2); + if (NS_FAILED(rv)) return rv; + } + else { + rv = rdf_BlockingWrite(aStream, kRDFDescriptionClose, + sizeof(kRDFDescriptionClose) - 1); + if (NS_FAILED(rv)) return rv; + } + } + else { + // If we saw _no_ child properties, then we can don't need a + // close-tag. + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(" />\n")); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +nsresult +nsRDFXMLSerializer::SerializeMember(nsIOutputStream* aStream, + nsIRDFResource* aContainer, + nsIRDFNode* aMember) +{ + // If it's a resource, then output a "<RDF:li RDF:resource=... />" + // tag, because we'll be dumping the resource separately. (We + // iterate thru all the resources in the datasource, + // remember?) Otherwise, output the literal value. + + nsCOMPtr<nsIRDFResource> resource; + nsCOMPtr<nsIRDFLiteral> literal; + nsCOMPtr<nsIRDFInt> number; + nsCOMPtr<nsIRDFDate> date; + +static const char kRDFLIOpen[] = " <RDF:li"; + nsresult rv = rdf_BlockingWrite(aStream, kRDFLIOpen, + sizeof(kRDFLIOpen) - 1); + if (NS_FAILED(rv)) return rv; + + if ((resource = do_QueryInterface(aMember)) != nullptr) { + nsAutoCString uri; + resource->GetValueUTF8(uri); + + rdf_MakeRelativeRef(mBaseURLSpec, uri); + rdf_EscapeAttributeValue(uri); + + rv = rdf_BlockingWrite(aStream, kRDFResource1, + sizeof(kRDFResource1) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, kRDFResource2, + sizeof(kRDFResource2) - 1); + if (NS_FAILED(rv)) return rv; + + goto no_close_tag; + } + else if ((literal = do_QueryInterface(aMember)) != nullptr) { + const char16_t *value; + literal->GetValueConst(&value); +static const char kRDFLIOpenGT[] = ">"; + // close the '<RDF:LI' before adding the literal + rv = rdf_BlockingWrite(aStream, kRDFLIOpenGT, + sizeof(kRDFLIOpenGT) - 1); + if (NS_FAILED(rv)) return rv; + + NS_ConvertUTF16toUTF8 s(value); + rdf_EscapeAmpersandsAndAngleBrackets(s); + + rv = rdf_BlockingWrite(aStream, s); + if (NS_FAILED(rv)) return rv; + } + else if ((number = do_QueryInterface(aMember)) != nullptr) { + int32_t value; + number->GetValue(&value); + + nsAutoCString n; + n.AppendInt(value); + + rv = rdf_BlockingWrite(aStream, kRDFParseTypeInteger, + sizeof(kRDFParseTypeInteger) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, n); + if (NS_FAILED(rv)) return rv; + } + else if ((date = do_QueryInterface(aMember)) != nullptr) { + PRTime value; + date->GetValue(&value); + + nsAutoCString s; + rdf_FormatDate(value, s); + + rv = rdf_BlockingWrite(aStream, kRDFParseTypeDate, + sizeof(kRDFParseTypeDate) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, s); + if (NS_FAILED(rv)) return rv; + } + else { + // XXX it doesn't support nsIRDFResource _or_ nsIRDFLiteral??? + // We should serialize nsIRDFInt, nsIRDFDate, etc... + NS_WARNING("unknown RDF node type"); + + rv = rdf_BlockingWrite(aStream, kRDFUnknown, sizeof(kRDFUnknown) - 1); + if (NS_FAILED(rv)) return rv; + } + + { +static const char kRDFLIClose[] = "</RDF:li>\n"; + rv = rdf_BlockingWrite(aStream, kRDFLIClose, sizeof(kRDFLIClose) - 1); + if (NS_FAILED(rv)) return rv; + } + + no_close_tag: + return NS_OK; +} + + +nsresult +nsRDFXMLSerializer::SerializeContainer(nsIOutputStream* aStream, + nsIRDFResource* aContainer) +{ + nsresult rv; + nsAutoCString tag; + + // Decide if it's a sequence, bag, or alternation, and print the + // appropriate tag-open sequence + + if (IsA(mDataSource, aContainer, kRDF_Bag)) { + tag.AssignLiteral("RDF:Bag"); + } + else if (IsA(mDataSource, aContainer, kRDF_Seq)) { + tag.AssignLiteral("RDF:Seq"); + } + else if (IsA(mDataSource, aContainer, kRDF_Alt)) { + tag.AssignLiteral("RDF:Alt"); + } + else { + NS_ASSERTION(false, "huh? this is _not_ a container."); + return NS_ERROR_UNEXPECTED; + } + + rv = rdf_BlockingWrite(aStream, " <", 3); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, tag); + if (NS_FAILED(rv)) return rv; + + + // Unfortunately, we always need to print out the identity of the + // resource, even if was constructed "anonymously". We need to do + // this because we never really know who else might be referring + // to it... + + nsAutoCString uri; + if (NS_SUCCEEDED(aContainer->GetValueUTF8(uri))) { + rdf_MakeRelativeRef(mBaseURLSpec, uri); + + rdf_EscapeAttributeValue(uri); + + if (uri.First() == '#') { + // Okay, it's actually identified as an element in the + // current document, not trying to decorate some absolute + // URI. We can use the 'ID=' attribute... + + uri.Cut(0, 1); // chop the '#' + rv = rdf_BlockingWrite(aStream, kIDAttr, sizeof(kIDAttr) - 1); + if (NS_FAILED(rv)) return rv; + } + else { + // We need to cheat and spit out an illegal 'about=' on + // the sequence. + rv = rdf_BlockingWrite(aStream, kAboutAttr, + sizeof(kAboutAttr) - 1); + if (NS_FAILED(rv)) return rv; + } + + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, "\"", 1); + if (NS_FAILED(rv)) return rv; + } + + rv = rdf_BlockingWrite(aStream, ">\n", 2); + if (NS_FAILED(rv)) return rv; + + // First iterate through each of the ordinal elements (the RDF/XML + // syntax doesn't allow us to place properties on RDF container + // elements). + nsCOMPtr<nsISimpleEnumerator> elements; + rv = NS_NewContainerEnumerator(mDataSource, aContainer, getter_AddRefs(elements)); + + if (NS_SUCCEEDED(rv)) { + while (1) { + bool hasMore; + rv = elements->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) break; + + if (! hasMore) + break; + + nsCOMPtr<nsISupports> isupports; + elements->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr<nsIRDFNode> element = do_QueryInterface(isupports); + NS_ASSERTION(element != nullptr, "not an nsIRDFNode"); + if (! element) + continue; + + SerializeMember(aStream, aContainer, element); + } + } + + // close the container tag + rv = rdf_BlockingWrite(aStream, " </", 4); + if (NS_FAILED(rv)) return rv; + tag.Append(">\n", 2); + rv = rdf_BlockingWrite(aStream, tag); + if (NS_FAILED(rv)) return rv; + + // Now, we iterate through _all_ of the arcs, in case someone has + // applied properties to the bag itself. These'll be placed in a + // separate RDF:Description element. + nsCOMPtr<nsISimpleEnumerator> arcs; + mDataSource->ArcLabelsOut(aContainer, getter_AddRefs(arcs)); + + bool wroteDescription = false; + while (! wroteDescription) { + bool hasMore = false; + rv = arcs->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) break; + + if (! hasMore) + break; + + nsIRDFResource* property; + rv = arcs->GetNext((nsISupports**) &property); + if (NS_FAILED(rv)) break; + + // If it's a membership property, then output a "LI" + // tag. Otherwise, output a property. + if (! IsContainerProperty(property)) { + rv = SerializeDescription(aStream, aContainer); + wroteDescription = true; + } + + NS_RELEASE(property); + if (NS_FAILED(rv)) + break; + } + + return NS_OK; +} + + +nsresult +nsRDFXMLSerializer::SerializePrologue(nsIOutputStream* aStream) +{ +static const char kXMLVersion[] = "<?xml version=\"1.0\"?>\n"; + + nsresult rv; + rv = rdf_BlockingWrite(aStream, kXMLVersion, sizeof(kXMLVersion) - 1); + if (NS_FAILED(rv)) return rv; + + // global name space declarations + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("<RDF:RDF ")); + if (NS_FAILED(rv)) return rv; + + nsNameSpaceMap::const_iterator first = mNameSpaces.first(); + nsNameSpaceMap::const_iterator last = mNameSpaces.last(); + for (nsNameSpaceMap::const_iterator entry = first; entry != last; ++entry) { + if (entry != first) { + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("\n ")); + if (NS_FAILED(rv)) return rv; + } + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("xmlns")); + if (NS_FAILED(rv)) return rv; + + if (entry->mPrefix) { + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(":")); + if (NS_FAILED(rv)) return rv; + nsAutoCString prefix; + entry->mPrefix->ToUTF8String(prefix); + rv = rdf_BlockingWrite(aStream, prefix); + if (NS_FAILED(rv)) return rv; + } + + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("=\"")); + if (NS_FAILED(rv)) return rv; + nsAutoCString uri(entry->mURI); + rdf_EscapeAttributeValue(uri); + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("\"")); + if (NS_FAILED(rv)) return rv; + } + + return rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(">\n")); +} + + +nsresult +nsRDFXMLSerializer::SerializeEpilogue(nsIOutputStream* aStream) +{ + return rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("</RDF:RDF>\n")); +} + +class QNameCollector final : public rdfITripleVisitor { +public: + NS_DECL_ISUPPORTS + NS_DECL_RDFITRIPLEVISITOR + explicit QNameCollector(nsRDFXMLSerializer* aParent) + : mParent(aParent){} +private: + ~QNameCollector() {} + nsRDFXMLSerializer* mParent; +}; + +NS_IMPL_ISUPPORTS(QNameCollector, rdfITripleVisitor) +nsresult +QNameCollector::Visit(nsIRDFNode* aSubject, nsIRDFResource* aPredicate, + nsIRDFNode* aObject, bool aTruthValue) +{ + if (aPredicate == mParent->kRDF_type) { + // try to get a type QName for aObject, should be a resource + nsCOMPtr<nsIRDFResource> resType = do_QueryInterface(aObject); + if (!resType) { + // ignore error + return NS_OK; + } + if (mParent->mQNames.Get(resType, nullptr)) { + return NS_OK; + } + mParent->RegisterQName(resType); + return NS_OK; + } + + if (mParent->mQNames.Get(aPredicate, nullptr)) { + return NS_OK; + } + if (aPredicate == mParent->kRDF_instanceOf || + aPredicate == mParent->kRDF_nextVal) + return NS_OK; + bool isOrdinal = false; + mParent->gRDFC->IsOrdinalProperty(aPredicate, &isOrdinal); + if (isOrdinal) + return NS_OK; + + mParent->RegisterQName(aPredicate); + + return NS_OK; +} + +nsresult +nsRDFXMLSerializer::CollectNamespaces() +{ + // Iterate over all Triples to get namespaces for subject resource types + // and Predicates and cache all the QNames we want to use. + nsCOMPtr<rdfITripleVisitor> collector = + new QNameCollector(this); + nsCOMPtr<rdfIDataSource> ds = do_QueryInterface(mDataSource); // XXX API + NS_ENSURE_TRUE(collector && ds, NS_ERROR_FAILURE); + return ds->VisitAllTriples(collector); +} + +//---------------------------------------------------------------------- + +NS_IMETHODIMP +nsRDFXMLSerializer::Serialize(nsIOutputStream* aStream) +{ + nsresult rv; + + rv = CollectNamespaces(); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsISimpleEnumerator> resources; + rv = mDataSource->GetAllResources(getter_AddRefs(resources)); + if (NS_FAILED(rv)) return rv; + + rv = SerializePrologue(aStream); + if (NS_FAILED(rv)) + return rv; + + while (1) { + bool hasMore = false; + resources->HasMoreElements(&hasMore); + if (! hasMore) + break; + + nsCOMPtr<nsISupports> isupports; + resources->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports); + if (! resource) + continue; + + if (IsA(mDataSource, resource, kRDF_Bag) || + IsA(mDataSource, resource, kRDF_Seq) || + IsA(mDataSource, resource, kRDF_Alt)) { + rv = SerializeContainer(aStream, resource); + } + else { + rv = SerializeDescription(aStream, resource); + } + + if (NS_FAILED(rv)) + break; + } + + rv = SerializeEpilogue(aStream); + + return rv; +} + + +bool +nsRDFXMLSerializer::IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType) +{ + nsresult rv; + + bool result; + rv = aDataSource->HasAssertion(aResource, kRDF_instanceOf, aType, true, &result); + if (NS_FAILED(rv)) return false; + + return result; +} |