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