michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 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: /* michael@0: michael@0: An implementation for an NGLayout-style content sink that knows how michael@0: to build an RDF content model from XML-serialized RDF. michael@0: michael@0: For more information on the RDF/XML syntax, michael@0: see http://www.w3.org/TR/REC-rdf-syntax/ michael@0: michael@0: This code is based on the final W3C Recommendation, michael@0: http://www.w3.org/TR/1999/REC-rdf-syntax-19990222. michael@0: michael@0: Open Issues ------------------ michael@0: michael@0: 1) factoring code with nsXMLContentSink - There's some amount of michael@0: common code between this and the HTML content sink. This will michael@0: increase as we support more and more HTML elements. How can code michael@0: from XML/HTML be factored? michael@0: michael@0: 2) We don't support the `parseType' attribute on the Description michael@0: tag; therefore, it is impossible to "inline" raw XML in this michael@0: implemenation. michael@0: michael@0: 3) We don't build the reifications at parse time due to the michael@0: footprint overhead it would incur for large RDF documents. (It michael@0: may be possible to attach a "reification" wrapper datasource that michael@0: would present this information at query-time.) Because of this, michael@0: the `bagID' attribute is not processed correctly. michael@0: michael@0: 4) No attempt is made to `resolve URIs' to a canonical form (the michael@0: specification hints that an implementation should do this). This michael@0: is omitted for the obvious reason that we can ill afford to michael@0: resolve each URI reference. michael@0: michael@0: */ michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsInterfaceHashtable.h" michael@0: #include "nsIContentSink.h" michael@0: #include "nsIRDFContainer.h" michael@0: #include "nsIRDFContainerUtils.h" michael@0: #include "nsIRDFContentSink.h" michael@0: #include "nsIRDFNode.h" michael@0: #include "nsIRDFService.h" michael@0: #include "nsIRDFXMLSink.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIXMLContentSink.h" michael@0: #include "nsRDFCID.h" michael@0: #include "nsTArray.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "prlog.h" michael@0: #include "rdf.h" michael@0: #include "rdfutil.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsIExpatSink.h" michael@0: #include "nsCRT.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsStaticAtom.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsIDTD.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /////////////////////////////////////////////////////////////////////// michael@0: michael@0: enum RDFContentSinkState { michael@0: eRDFContentSinkState_InProlog, michael@0: eRDFContentSinkState_InDocumentElement, michael@0: eRDFContentSinkState_InDescriptionElement, michael@0: eRDFContentSinkState_InContainerElement, michael@0: eRDFContentSinkState_InPropertyElement, michael@0: eRDFContentSinkState_InMemberElement, michael@0: eRDFContentSinkState_InEpilog michael@0: }; michael@0: michael@0: enum RDFContentSinkParseMode { michael@0: eRDFContentSinkParseMode_Resource, michael@0: eRDFContentSinkParseMode_Literal, michael@0: eRDFContentSinkParseMode_Int, michael@0: eRDFContentSinkParseMode_Date michael@0: }; michael@0: michael@0: typedef michael@0: NS_STDCALL_FUNCPROTO(nsresult, michael@0: nsContainerTestFn, michael@0: nsIRDFContainerUtils, IsAlt, michael@0: (nsIRDFDataSource*, nsIRDFResource*, bool*)); michael@0: michael@0: typedef michael@0: NS_STDCALL_FUNCPROTO(nsresult, michael@0: nsMakeContainerFn, michael@0: nsIRDFContainerUtils, MakeAlt, michael@0: (nsIRDFDataSource*, nsIRDFResource*, nsIRDFContainer**)); michael@0: michael@0: class RDFContentSinkImpl : public nsIRDFContentSink, michael@0: public nsIExpatSink michael@0: { michael@0: public: michael@0: RDFContentSinkImpl(); michael@0: virtual ~RDFContentSinkImpl(); michael@0: michael@0: // nsISupports michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIEXPATSINK michael@0: michael@0: // nsIContentSink michael@0: NS_IMETHOD WillParse(void); michael@0: NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode); michael@0: NS_IMETHOD DidBuildModel(bool aTerminated); michael@0: NS_IMETHOD WillInterrupt(void); michael@0: NS_IMETHOD WillResume(void); michael@0: NS_IMETHOD SetParser(nsParserBase* aParser); michael@0: virtual void FlushPendingNotifications(mozFlushType aType) { } michael@0: NS_IMETHOD SetDocumentCharset(nsACString& aCharset) { return NS_OK; } michael@0: virtual nsISupports *GetTarget() { return nullptr; } michael@0: michael@0: // nsIRDFContentSink michael@0: NS_IMETHOD Init(nsIURI* aURL); michael@0: NS_IMETHOD SetDataSource(nsIRDFDataSource* aDataSource); michael@0: NS_IMETHOD GetDataSource(nsIRDFDataSource*& aDataSource); michael@0: michael@0: // pseudo constants michael@0: static int32_t gRefCnt; michael@0: static nsIRDFService* gRDFService; michael@0: static nsIRDFContainerUtils* gRDFContainerUtils; michael@0: static nsIRDFResource* kRDF_type; michael@0: static nsIRDFResource* kRDF_instanceOf; // XXX should be RDF:type michael@0: static nsIRDFResource* kRDF_Alt; michael@0: static nsIRDFResource* kRDF_Bag; michael@0: static nsIRDFResource* kRDF_Seq; michael@0: static nsIRDFResource* kRDF_nextVal; michael@0: michael@0: #define RDF_ATOM(name_, value_) static nsIAtom* name_; michael@0: #include "nsRDFContentSinkAtomList.h" michael@0: #undef RDF_ATOM michael@0: michael@0: typedef struct ContainerInfo { michael@0: nsIRDFResource** mType; michael@0: nsContainerTestFn mTestFn; michael@0: nsMakeContainerFn mMakeFn; michael@0: } ContainerInfo; michael@0: michael@0: protected: michael@0: // Text management michael@0: void ParseText(nsIRDFNode **aResult); michael@0: michael@0: nsresult FlushText(); michael@0: nsresult AddText(const char16_t* aText, int32_t aLength); michael@0: michael@0: // RDF-specific parsing michael@0: nsresult OpenRDF(const char16_t* aName); michael@0: nsresult OpenObject(const char16_t* aName ,const char16_t** aAttributes); michael@0: nsresult OpenProperty(const char16_t* aName, const char16_t** aAttributes); michael@0: nsresult OpenMember(const char16_t* aName, const char16_t** aAttributes); michael@0: nsresult OpenValue(const char16_t* aName, const char16_t** aAttributes); michael@0: michael@0: nsresult GetIdAboutAttribute(const char16_t** aAttributes, nsIRDFResource** aResource, bool* aIsAnonymous = nullptr); michael@0: nsresult GetResourceAttribute(const char16_t** aAttributes, nsIRDFResource** aResource); michael@0: nsresult AddProperties(const char16_t** aAttributes, nsIRDFResource* aSubject, int32_t* aCount = nullptr); michael@0: void SetParseMode(const char16_t **aAttributes); michael@0: michael@0: char16_t* mText; michael@0: int32_t mTextLength; michael@0: int32_t mTextSize; michael@0: michael@0: /** michael@0: * From the set of given attributes, this method extracts the michael@0: * namespace definitions and feeds them to the datasource. michael@0: * These can then be suggested to the serializer to be used again. michael@0: * Hopefully, this will keep namespace definitions intact in a michael@0: * parse - serialize cycle. michael@0: */ michael@0: void RegisterNamespaces(const char16_t **aAttributes); michael@0: michael@0: /** michael@0: * Extracts the localname from aExpatName, the name that the Expat parser michael@0: * passes us. michael@0: * aLocalName will contain the localname in aExpatName. michael@0: * The return value is a dependent string containing just the namespace. michael@0: */ michael@0: const nsDependentSubstring SplitExpatName(const char16_t *aExpatName, michael@0: nsIAtom **aLocalName); michael@0: michael@0: enum eContainerType { eBag, eSeq, eAlt }; michael@0: nsresult InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer); michael@0: nsresult ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer); michael@0: michael@0: // The datasource in which we're assigning assertions michael@0: nsCOMPtr mDataSource; michael@0: michael@0: // A hash of all the node IDs referred to michael@0: nsInterfaceHashtable mNodeIDMap; michael@0: michael@0: // The current state of the content sink michael@0: RDFContentSinkState mState; michael@0: RDFContentSinkParseMode mParseMode; michael@0: michael@0: // content stack management michael@0: int32_t michael@0: PushContext(nsIRDFResource *aContext, michael@0: RDFContentSinkState aState, michael@0: RDFContentSinkParseMode aParseMode); michael@0: michael@0: nsresult michael@0: PopContext(nsIRDFResource *&aContext, michael@0: RDFContentSinkState &aState, michael@0: RDFContentSinkParseMode &aParseMode); michael@0: michael@0: nsIRDFResource* GetContextElement(int32_t ancestor = 0); michael@0: michael@0: michael@0: struct RDFContextStackElement { michael@0: nsCOMPtr mResource; michael@0: RDFContentSinkState mState; michael@0: RDFContentSinkParseMode mParseMode; michael@0: }; michael@0: michael@0: nsAutoTArray* mContextStack; michael@0: michael@0: nsIURI* mDocumentURL; michael@0: michael@0: private: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo* gLog; michael@0: #endif michael@0: }; michael@0: michael@0: int32_t RDFContentSinkImpl::gRefCnt = 0; michael@0: nsIRDFService* RDFContentSinkImpl::gRDFService; michael@0: nsIRDFContainerUtils* RDFContentSinkImpl::gRDFContainerUtils; michael@0: nsIRDFResource* RDFContentSinkImpl::kRDF_type; michael@0: nsIRDFResource* RDFContentSinkImpl::kRDF_instanceOf; michael@0: nsIRDFResource* RDFContentSinkImpl::kRDF_Alt; michael@0: nsIRDFResource* RDFContentSinkImpl::kRDF_Bag; michael@0: nsIRDFResource* RDFContentSinkImpl::kRDF_Seq; michael@0: nsIRDFResource* RDFContentSinkImpl::kRDF_nextVal; michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* RDFContentSinkImpl::gLog; michael@0: #endif michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: michael@0: #define RDF_ATOM(name_, value_) nsIAtom* RDFContentSinkImpl::name_; michael@0: #include "nsRDFContentSinkAtomList.h" michael@0: #undef RDF_ATOM michael@0: michael@0: #define RDF_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_) michael@0: #include "nsRDFContentSinkAtomList.h" michael@0: #undef RDF_ATOM michael@0: michael@0: static const nsStaticAtom rdf_atoms[] = { michael@0: #define RDF_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &RDFContentSinkImpl::name_), michael@0: #include "nsRDFContentSinkAtomList.h" michael@0: #undef RDF_ATOM michael@0: }; michael@0: michael@0: RDFContentSinkImpl::RDFContentSinkImpl() michael@0: : mText(nullptr), michael@0: mTextLength(0), michael@0: mTextSize(0), michael@0: mState(eRDFContentSinkState_InProlog), michael@0: mParseMode(eRDFContentSinkParseMode_Literal), michael@0: mContextStack(nullptr), michael@0: mDocumentURL(nullptr) michael@0: { michael@0: if (gRefCnt++ == 0) { michael@0: NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); michael@0: nsresult rv = CallGetService(kRDFServiceCID, &gRDFService); michael@0: michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"), michael@0: &kRDF_type); michael@0: rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"), michael@0: &kRDF_instanceOf); michael@0: rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"), michael@0: &kRDF_Alt); michael@0: rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"), michael@0: &kRDF_Bag); michael@0: rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"), michael@0: &kRDF_Seq); michael@0: rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"), michael@0: &kRDF_nextVal); michael@0: } michael@0: michael@0: NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); michael@0: rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); michael@0: michael@0: NS_RegisterStaticAtoms(rdf_atoms); michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (! gLog) michael@0: gLog = PR_NewLogModule("nsRDFContentSink"); michael@0: #endif michael@0: } michael@0: michael@0: michael@0: RDFContentSinkImpl::~RDFContentSinkImpl() michael@0: { michael@0: #ifdef DEBUG_REFS michael@0: --gInstanceCount; michael@0: fprintf(stdout, "%d - RDF: RDFContentSinkImpl\n", gInstanceCount); michael@0: #endif michael@0: michael@0: NS_IF_RELEASE(mDocumentURL); michael@0: michael@0: if (mContextStack) { michael@0: PR_LOG(gLog, PR_LOG_WARNING, michael@0: ("rdfxml: warning! unclosed tag")); michael@0: michael@0: // XXX we should never need to do this, but, we'll write the michael@0: // code all the same. If someone left the content stack dirty, michael@0: // pop all the elements off the stack and release them. michael@0: int32_t i = mContextStack->Length(); michael@0: while (0 < i--) { michael@0: nsIRDFResource* resource = nullptr; michael@0: RDFContentSinkState state; michael@0: RDFContentSinkParseMode parseMode; michael@0: PopContext(resource, state, parseMode); michael@0: michael@0: #ifdef PR_LOGGING michael@0: // print some fairly useless debugging info michael@0: // XXX we should save line numbers on the context stack: this'd michael@0: // be about 1000x more helpful. michael@0: if (resource) { michael@0: nsXPIDLCString uri; michael@0: resource->GetValue(getter_Copies(uri)); michael@0: PR_LOG(gLog, PR_LOG_NOTICE, michael@0: ("rdfxml: uri=%s", (const char*) uri)); michael@0: } michael@0: #endif michael@0: michael@0: NS_IF_RELEASE(resource); michael@0: } michael@0: michael@0: delete mContextStack; michael@0: } michael@0: moz_free(mText); michael@0: michael@0: michael@0: if (--gRefCnt == 0) { michael@0: NS_IF_RELEASE(gRDFService); michael@0: NS_IF_RELEASE(gRDFContainerUtils); michael@0: NS_IF_RELEASE(kRDF_type); michael@0: NS_IF_RELEASE(kRDF_instanceOf); michael@0: NS_IF_RELEASE(kRDF_Alt); michael@0: NS_IF_RELEASE(kRDF_Bag); michael@0: NS_IF_RELEASE(kRDF_Seq); michael@0: NS_IF_RELEASE(kRDF_nextVal); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // nsISupports interface michael@0: michael@0: NS_IMPL_ADDREF(RDFContentSinkImpl) michael@0: NS_IMPL_RELEASE(RDFContentSinkImpl) michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::QueryInterface(REFNSIID iid, void** result) michael@0: { michael@0: NS_PRECONDITION(result, "null ptr"); michael@0: if (! result) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: NS_DEFINE_IID(kIContentSinkIID, NS_ICONTENT_SINK_IID); michael@0: NS_DEFINE_IID(kIExpatSinkIID, NS_IEXPATSINK_IID); michael@0: NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); michael@0: NS_DEFINE_IID(kIXMLContentSinkIID, NS_IXMLCONTENT_SINK_IID); michael@0: NS_DEFINE_IID(kIRDFContentSinkIID, NS_IRDFCONTENTSINK_IID); michael@0: michael@0: *result = nullptr; michael@0: if (iid.Equals(kIRDFContentSinkIID) || michael@0: iid.Equals(kIXMLContentSinkIID) || michael@0: iid.Equals(kIContentSinkIID) || michael@0: iid.Equals(kISupportsIID)) { michael@0: *result = static_cast(this); michael@0: AddRef(); michael@0: return NS_OK; michael@0: } michael@0: else if (iid.Equals(kIExpatSinkIID)) { michael@0: *result = static_cast(this); michael@0: AddRef(); michael@0: return NS_OK; michael@0: } michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::HandleStartElement(const char16_t *aName, michael@0: const char16_t **aAtts, michael@0: uint32_t aAttsCount, michael@0: int32_t aIndex, michael@0: uint32_t aLineNumber) michael@0: { michael@0: FlushText(); michael@0: michael@0: nsresult rv = NS_ERROR_UNEXPECTED; // XXX michael@0: michael@0: RegisterNamespaces(aAtts); michael@0: michael@0: switch (mState) { michael@0: case eRDFContentSinkState_InProlog: michael@0: rv = OpenRDF(aName); michael@0: break; michael@0: michael@0: case eRDFContentSinkState_InDocumentElement: michael@0: rv = OpenObject(aName,aAtts); michael@0: break; michael@0: michael@0: case eRDFContentSinkState_InDescriptionElement: michael@0: rv = OpenProperty(aName,aAtts); michael@0: break; michael@0: michael@0: case eRDFContentSinkState_InContainerElement: michael@0: rv = OpenMember(aName,aAtts); michael@0: break; michael@0: michael@0: case eRDFContentSinkState_InPropertyElement: michael@0: case eRDFContentSinkState_InMemberElement: michael@0: rv = OpenValue(aName,aAtts); michael@0: break; michael@0: michael@0: case eRDFContentSinkState_InEpilog: michael@0: PR_LOG(gLog, PR_LOG_WARNING, michael@0: ("rdfxml: unexpected content in epilog at line %d", michael@0: aLineNumber)); michael@0: break; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::HandleEndElement(const char16_t *aName) michael@0: { michael@0: FlushText(); michael@0: michael@0: nsIRDFResource* resource; michael@0: if (NS_FAILED(PopContext(resource, mState, mParseMode))) { michael@0: // XXX parser didn't catch unmatched tags? michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gLog, PR_LOG_WARNING)) { michael@0: nsAutoString tagStr(aName); michael@0: char* tagCStr = ToNewCString(tagStr); michael@0: michael@0: PR_LogPrint michael@0: ("rdfxml: extra close tag '%s' at line %d", michael@0: tagCStr, 0/*XXX fix me */); michael@0: michael@0: NS_Free(tagCStr); michael@0: } michael@0: #endif michael@0: michael@0: return NS_ERROR_UNEXPECTED; // XXX michael@0: } michael@0: michael@0: // If we've just popped a member or property element, _now_ is the michael@0: // time to add that element to the graph. michael@0: switch (mState) { michael@0: case eRDFContentSinkState_InMemberElement: michael@0: { michael@0: nsCOMPtr container; michael@0: NS_NewRDFContainer(getter_AddRefs(container)); michael@0: container->Init(mDataSource, GetContextElement(1)); michael@0: container->AppendElement(resource); michael@0: } michael@0: break; michael@0: michael@0: case eRDFContentSinkState_InPropertyElement: michael@0: { michael@0: mDataSource->Assert(GetContextElement(1), GetContextElement(0), resource, true); michael@0: } break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: if (mContextStack->IsEmpty()) michael@0: mState = eRDFContentSinkState_InEpilog; michael@0: michael@0: NS_IF_RELEASE(resource); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::HandleComment(const char16_t *aName) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::HandleCDataSection(const char16_t *aData, michael@0: uint32_t aLength) michael@0: { michael@0: return aData ? AddText(aData, aLength) : NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::HandleDoctypeDecl(const nsAString & aSubset, michael@0: const nsAString & aName, michael@0: const nsAString & aSystemId, michael@0: const nsAString & aPublicId, michael@0: nsISupports* aCatalogData) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::HandleCharacterData(const char16_t *aData, michael@0: uint32_t aLength) michael@0: { michael@0: return aData ? AddText(aData, aLength) : NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::HandleProcessingInstruction(const char16_t *aTarget, michael@0: const char16_t *aData) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::HandleXMLDeclaration(const char16_t *aVersion, michael@0: const char16_t *aEncoding, michael@0: int32_t aStandalone) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::ReportError(const char16_t* aErrorText, michael@0: const char16_t* aSourceText, michael@0: nsIScriptError *aError, michael@0: bool *_retval) michael@0: { michael@0: NS_PRECONDITION(aError && aSourceText && aErrorText, "Check arguments!!!"); michael@0: michael@0: // The expat driver should report the error. michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // nsIContentSink interface michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::WillParse(void) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::WillBuildModel(nsDTDMode) michael@0: { michael@0: if (mDataSource) { michael@0: nsCOMPtr sink = do_QueryInterface(mDataSource); michael@0: if (sink) michael@0: return sink->BeginLoad(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::DidBuildModel(bool aTerminated) michael@0: { michael@0: if (mDataSource) { michael@0: nsCOMPtr sink = do_QueryInterface(mDataSource); michael@0: if (sink) michael@0: return sink->EndLoad(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::WillInterrupt(void) michael@0: { michael@0: if (mDataSource) { michael@0: nsCOMPtr sink = do_QueryInterface(mDataSource); michael@0: if (sink) michael@0: return sink->Interrupt(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::WillResume(void) michael@0: { michael@0: if (mDataSource) { michael@0: nsCOMPtr sink = do_QueryInterface(mDataSource); michael@0: if (sink) michael@0: return sink->Resume(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::SetParser(nsParserBase* aParser) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // nsIRDFContentSink interface michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::Init(nsIURI* aURL) michael@0: { michael@0: NS_PRECONDITION(aURL != nullptr, "null ptr"); michael@0: if (! aURL) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: mDocumentURL = aURL; michael@0: NS_ADDREF(aURL); michael@0: michael@0: mState = eRDFContentSinkState_InProlog; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::SetDataSource(nsIRDFDataSource* aDataSource) michael@0: { michael@0: NS_PRECONDITION(aDataSource != nullptr, "SetDataSource null ptr"); michael@0: mDataSource = aDataSource; michael@0: NS_ASSERTION(mDataSource != nullptr,"Couldn't QI RDF DataSource"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: RDFContentSinkImpl::GetDataSource(nsIRDFDataSource*& aDataSource) michael@0: { michael@0: aDataSource = mDataSource; michael@0: NS_IF_ADDREF(aDataSource); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // Text buffering michael@0: michael@0: static bool michael@0: rdf_IsDataInBuffer(char16_t* buffer, int32_t length) michael@0: { michael@0: for (int32_t i = 0; i < length; ++i) { michael@0: if (buffer[i] == ' ' || michael@0: buffer[i] == '\t' || michael@0: buffer[i] == '\n' || michael@0: buffer[i] == '\r') michael@0: continue; michael@0: michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: RDFContentSinkImpl::ParseText(nsIRDFNode **aResult) michael@0: { michael@0: // XXXwaterson wasteful, but we'd need to make a copy anyway to be michael@0: // able to call nsIRDFService::Get[Resource|Literal|...](). michael@0: nsAutoString value; michael@0: value.Append(mText, mTextLength); michael@0: value.Trim(" \t\n\r"); michael@0: michael@0: switch (mParseMode) { michael@0: case eRDFContentSinkParseMode_Literal: michael@0: { michael@0: nsIRDFLiteral *result; michael@0: gRDFService->GetLiteral(value.get(), &result); michael@0: *aResult = result; michael@0: } michael@0: break; michael@0: michael@0: case eRDFContentSinkParseMode_Resource: michael@0: { michael@0: nsIRDFResource *result; michael@0: gRDFService->GetUnicodeResource(value, &result); michael@0: *aResult = result; michael@0: } michael@0: break; michael@0: michael@0: case eRDFContentSinkParseMode_Int: michael@0: { michael@0: nsresult err; michael@0: int32_t i = value.ToInteger(&err); michael@0: nsIRDFInt *result; michael@0: gRDFService->GetIntLiteral(i, &result); michael@0: *aResult = result; michael@0: } michael@0: break; michael@0: michael@0: case eRDFContentSinkParseMode_Date: michael@0: { michael@0: PRTime t = rdf_ParseDate(nsDependentCString(NS_LossyConvertUTF16toASCII(value).get(), value.Length())); michael@0: nsIRDFDate *result; michael@0: gRDFService->GetDateLiteral(t, &result); michael@0: *aResult = result; michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: NS_NOTREACHED("unknown parse type"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::FlushText() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: if (0 != mTextLength) { michael@0: if (rdf_IsDataInBuffer(mText, mTextLength)) { michael@0: // XXX if there's anything but whitespace, then we'll michael@0: // create a text node. michael@0: michael@0: switch (mState) { michael@0: case eRDFContentSinkState_InMemberElement: { michael@0: nsCOMPtr node; michael@0: ParseText(getter_AddRefs(node)); michael@0: michael@0: nsCOMPtr container; michael@0: NS_NewRDFContainer(getter_AddRefs(container)); michael@0: container->Init(mDataSource, GetContextElement(1)); michael@0: michael@0: container->AppendElement(node); michael@0: } break; michael@0: michael@0: case eRDFContentSinkState_InPropertyElement: { michael@0: nsCOMPtr node; michael@0: ParseText(getter_AddRefs(node)); michael@0: michael@0: mDataSource->Assert(GetContextElement(1), GetContextElement(0), node, true); michael@0: } break; michael@0: michael@0: default: michael@0: // just ignore it michael@0: break; michael@0: } michael@0: } michael@0: mTextLength = 0; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::AddText(const char16_t* aText, int32_t aLength) michael@0: { michael@0: // Create buffer when we first need it michael@0: if (0 == mTextSize) { michael@0: mText = (char16_t *) moz_malloc(sizeof(char16_t) * 4096); michael@0: if (!mText) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: mTextSize = 4096; michael@0: } michael@0: michael@0: // Copy data from string into our buffer; grow the buffer as needed. michael@0: // It never shrinks, but since the content sink doesn't stick around, michael@0: // this shouldn't be a bloat issue. michael@0: int32_t amount = mTextSize - mTextLength; michael@0: if (amount < aLength) { michael@0: // Grow the buffer by at least a factor of two to prevent thrashing. michael@0: // Since PR_REALLOC will leave mText intact if the call fails, michael@0: // don't clobber mText or mTextSize until the new mem is allocated. michael@0: int32_t newSize = (2 * mTextSize > (mTextSize + aLength)) ? michael@0: (2 * mTextSize) : (mTextSize + aLength); michael@0: char16_t* newText = michael@0: (char16_t *) moz_realloc(mText, sizeof(char16_t) * newSize); michael@0: if (!newText) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: mTextSize = newSize; michael@0: mText = newText; michael@0: } michael@0: memcpy(&mText[mTextLength], aText, sizeof(char16_t) * aLength); michael@0: mTextLength += aLength; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: rdf_RequiresAbsoluteURI(const nsString& uri) michael@0: { michael@0: // cheap shot at figuring out if this requires an absolute url translation michael@0: return !(StringBeginsWith(uri, NS_LITERAL_STRING("urn:")) || michael@0: StringBeginsWith(uri, NS_LITERAL_STRING("chrome:"))); michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::GetIdAboutAttribute(const char16_t** aAttributes, michael@0: nsIRDFResource** aResource, michael@0: bool* aIsAnonymous) michael@0: { michael@0: // This corresponds to the dirty work of production [6.5] michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsAutoString nodeID; michael@0: michael@0: nsCOMPtr localName; michael@0: for (; *aAttributes; aAttributes += 2) { michael@0: const nsDependentSubstring& nameSpaceURI = michael@0: SplitExpatName(aAttributes[0], getter_AddRefs(localName)); michael@0: michael@0: // We'll accept either `ID' or `rdf:ID' (ibid with `about' or michael@0: // `rdf:about') in the spirit of being liberal towards the michael@0: // input that we receive. michael@0: if (!nameSpaceURI.IsEmpty() && michael@0: !nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) { michael@0: continue; michael@0: } michael@0: michael@0: // XXX you can't specify both, but we'll just pick up the michael@0: // first thing that was specified and ignore the other. michael@0: michael@0: if (localName == kAboutAtom) { michael@0: if (aIsAnonymous) michael@0: *aIsAnonymous = false; michael@0: michael@0: nsAutoString relURI(aAttributes[1]); michael@0: if (rdf_RequiresAbsoluteURI(relURI)) { michael@0: nsAutoCString uri; michael@0: rv = mDocumentURL->Resolve(NS_ConvertUTF16toUTF8(aAttributes[1]), uri); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return gRDFService->GetResource(uri, michael@0: aResource); michael@0: } michael@0: return gRDFService->GetResource(NS_ConvertUTF16toUTF8(aAttributes[1]), michael@0: aResource); michael@0: } michael@0: else if (localName == kIdAtom) { michael@0: if (aIsAnonymous) michael@0: *aIsAnonymous = false; michael@0: // In the spirit of leniency, we do not bother trying to michael@0: // enforce that this be a valid "XML Name" (see michael@0: // http://www.w3.org/TR/REC-xml#NT-Nmtoken), as per michael@0: // 6.21. If we wanted to, this would be where to do it. michael@0: michael@0: // Construct an in-line resource whose URI is the michael@0: // document's URI plus the XML name specified in the ID michael@0: // attribute. michael@0: nsAutoCString name; michael@0: nsAutoCString ref('#'); michael@0: AppendUTF16toUTF8(aAttributes[1], ref); michael@0: michael@0: rv = mDocumentURL->Resolve(ref, name); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return gRDFService->GetResource(name, aResource); michael@0: } michael@0: else if (localName == kNodeIdAtom) { michael@0: nodeID.Assign(aAttributes[1]); michael@0: } michael@0: else if (localName == kAboutEachAtom) { michael@0: // XXX we don't deal with aboutEach... michael@0: //PR_LOG(gLog, PR_LOG_WARNING, michael@0: // ("rdfxml: ignoring aboutEach at line %d", michael@0: // aNode.GetSourceLineNumber())); michael@0: } michael@0: } michael@0: michael@0: // Otherwise, we couldn't find anything, so just gensym one... michael@0: if (aIsAnonymous) michael@0: *aIsAnonymous = true; michael@0: michael@0: // If nodeID is present, check if we already know about it. If we've seen michael@0: // the nodeID before, use the same resource, otherwise generate a new one. michael@0: if (!nodeID.IsEmpty()) { michael@0: mNodeIDMap.Get(nodeID,aResource); michael@0: michael@0: if (!*aResource) { michael@0: rv = gRDFService->GetAnonymousResource(aResource); michael@0: mNodeIDMap.Put(nodeID,*aResource); michael@0: } michael@0: } michael@0: else { michael@0: rv = gRDFService->GetAnonymousResource(aResource); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::GetResourceAttribute(const char16_t** aAttributes, michael@0: nsIRDFResource** aResource) michael@0: { michael@0: nsCOMPtr localName; michael@0: michael@0: nsAutoString nodeID; michael@0: michael@0: for (; *aAttributes; aAttributes += 2) { michael@0: const nsDependentSubstring& nameSpaceURI = michael@0: SplitExpatName(aAttributes[0], getter_AddRefs(localName)); michael@0: michael@0: // We'll accept `resource' or `rdf:resource', under the spirit michael@0: // that we should be liberal towards the input that we michael@0: // receive. michael@0: if (!nameSpaceURI.IsEmpty() && michael@0: !nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) { michael@0: continue; michael@0: } michael@0: michael@0: // XXX you can't specify both, but we'll just pick up the michael@0: // first thing that was specified and ignore the other. michael@0: michael@0: if (localName == kResourceAtom) { michael@0: // XXX Take the URI and make it fully qualified by michael@0: // sticking it into the document's URL. This may not be michael@0: // appropriate... michael@0: nsAutoString relURI(aAttributes[1]); michael@0: if (rdf_RequiresAbsoluteURI(relURI)) { michael@0: nsresult rv; michael@0: nsAutoCString uri; michael@0: michael@0: rv = mDocumentURL->Resolve(NS_ConvertUTF16toUTF8(aAttributes[1]), uri); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return gRDFService->GetResource(uri, aResource); michael@0: } michael@0: return gRDFService->GetResource(NS_ConvertUTF16toUTF8(aAttributes[1]), michael@0: aResource); michael@0: } michael@0: else if (localName == kNodeIdAtom) { michael@0: nodeID.Assign(aAttributes[1]); michael@0: } michael@0: } michael@0: michael@0: // If nodeID is present, check if we already know about it. If we've seen michael@0: // the nodeID before, use the same resource, otherwise generate a new one. michael@0: if (!nodeID.IsEmpty()) { michael@0: mNodeIDMap.Get(nodeID,aResource); michael@0: michael@0: if (!*aResource) { michael@0: nsresult rv; michael@0: rv = gRDFService->GetAnonymousResource(aResource); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: mNodeIDMap.Put(nodeID,*aResource); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::AddProperties(const char16_t** aAttributes, michael@0: nsIRDFResource* aSubject, michael@0: int32_t* aCount) michael@0: { michael@0: if (aCount) michael@0: *aCount = 0; michael@0: michael@0: nsCOMPtr localName; michael@0: for (; *aAttributes; aAttributes += 2) { michael@0: const nsDependentSubstring& nameSpaceURI = michael@0: SplitExpatName(aAttributes[0], getter_AddRefs(localName)); michael@0: michael@0: // skip 'xmlns' directives, these are "meta" information michael@0: if (nameSpaceURI.EqualsLiteral("http://www.w3.org/2000/xmlns/")) { michael@0: continue; michael@0: } michael@0: michael@0: // skip `about', `ID', `resource', and 'nodeID' attributes (either with or michael@0: // without the `rdf:' prefix); these are all "special" and michael@0: // should've been dealt with by the caller. michael@0: if (localName == kAboutAtom || localName == kIdAtom || michael@0: localName == kResourceAtom || localName == kNodeIdAtom) { michael@0: if (nameSpaceURI.IsEmpty() || michael@0: nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) michael@0: continue; michael@0: } michael@0: michael@0: // Skip `parseType', `RDF:parseType', and `NC:parseType'. This michael@0: // is meta-information that will be handled in SetParseMode. michael@0: if (localName == kParseTypeAtom) { michael@0: if (nameSpaceURI.IsEmpty() || michael@0: nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) || michael@0: nameSpaceURI.EqualsLiteral(NC_NAMESPACE_URI)) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: NS_ConvertUTF16toUTF8 propertyStr(nameSpaceURI); michael@0: propertyStr.Append(nsAtomCString(localName)); michael@0: michael@0: // Add the assertion to RDF michael@0: nsCOMPtr property; michael@0: gRDFService->GetResource(propertyStr, getter_AddRefs(property)); michael@0: michael@0: nsCOMPtr target; michael@0: gRDFService->GetLiteral(aAttributes[1], michael@0: getter_AddRefs(target)); michael@0: michael@0: mDataSource->Assert(aSubject, property, target, true); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: RDFContentSinkImpl::SetParseMode(const char16_t **aAttributes) michael@0: { michael@0: nsCOMPtr localName; michael@0: for (; *aAttributes; aAttributes += 2) { michael@0: const nsDependentSubstring& nameSpaceURI = michael@0: SplitExpatName(aAttributes[0], getter_AddRefs(localName)); michael@0: michael@0: if (localName == kParseTypeAtom) { michael@0: nsDependentString v(aAttributes[1]); michael@0: michael@0: if (nameSpaceURI.IsEmpty() || michael@0: nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) { michael@0: if (v.EqualsLiteral("Resource")) michael@0: mParseMode = eRDFContentSinkParseMode_Resource; michael@0: michael@0: break; michael@0: } michael@0: else if (nameSpaceURI.EqualsLiteral(NC_NAMESPACE_URI)) { michael@0: if (v.EqualsLiteral("Date")) michael@0: mParseMode = eRDFContentSinkParseMode_Date; michael@0: else if (v.EqualsLiteral("Integer")) michael@0: mParseMode = eRDFContentSinkParseMode_Int; michael@0: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // RDF-specific routines used to build the model michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::OpenRDF(const char16_t* aName) michael@0: { michael@0: // ensure that we're actually reading RDF by making sure that the michael@0: // opening tag is , where "rdf:" corresponds to whatever michael@0: // they've declared the standard RDF namespace to be. michael@0: nsCOMPtr localName; michael@0: const nsDependentSubstring& nameSpaceURI = michael@0: SplitExpatName(aName, getter_AddRefs(localName)); michael@0: michael@0: if (!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) || localName != kRDFAtom) { michael@0: // PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: // ("rdfxml: expected RDF:RDF at line %d", michael@0: // aNode.GetSourceLineNumber())); michael@0: michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: PushContext(nullptr, mState, mParseMode); michael@0: mState = eRDFContentSinkState_InDocumentElement; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::OpenObject(const char16_t* aName, michael@0: const char16_t** aAttributes) michael@0: { michael@0: // an "object" non-terminal is either a "description", a "typed michael@0: // node", or a "container", so this change the content sink's michael@0: // state appropriately. michael@0: nsCOMPtr localName; michael@0: const nsDependentSubstring& nameSpaceURI = michael@0: SplitExpatName(aName, getter_AddRefs(localName)); michael@0: michael@0: // Figure out the URI of this object, and create an RDF node for it. michael@0: nsCOMPtr source; michael@0: GetIdAboutAttribute(aAttributes, getter_AddRefs(source)); michael@0: michael@0: // If there is no `ID' or `about', then there's not much we can do. michael@0: if (! source) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Push the element onto the context stack michael@0: PushContext(source, mState, mParseMode); michael@0: michael@0: // Now figure out what kind of state transition we need to michael@0: // make. We'll either be going into a mode where we parse a michael@0: // description or a container. michael@0: bool isaTypedNode = true; michael@0: michael@0: if (nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) { michael@0: isaTypedNode = false; michael@0: michael@0: if (localName == kDescriptionAtom) { michael@0: // it's a description michael@0: mState = eRDFContentSinkState_InDescriptionElement; michael@0: } michael@0: else if (localName == kBagAtom) { michael@0: // it's a bag container michael@0: InitContainer(kRDF_Bag, source); michael@0: mState = eRDFContentSinkState_InContainerElement; michael@0: } michael@0: else if (localName == kSeqAtom) { michael@0: // it's a seq container michael@0: InitContainer(kRDF_Seq, source); michael@0: mState = eRDFContentSinkState_InContainerElement; michael@0: } michael@0: else if (localName == kAltAtom) { michael@0: // it's an alt container michael@0: InitContainer(kRDF_Alt, source); michael@0: mState = eRDFContentSinkState_InContainerElement; michael@0: } michael@0: else { michael@0: // heh, that's not *in* the RDF namespace: just treat it michael@0: // like a typed node michael@0: isaTypedNode = true; michael@0: } michael@0: } michael@0: michael@0: if (isaTypedNode) { michael@0: NS_ConvertUTF16toUTF8 typeStr(nameSpaceURI); michael@0: typeStr.Append(nsAtomCString(localName)); michael@0: michael@0: nsCOMPtr type; michael@0: nsresult rv = gRDFService->GetResource(typeStr, getter_AddRefs(type)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mDataSource->Assert(source, kRDF_type, type, true); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mState = eRDFContentSinkState_InDescriptionElement; michael@0: } michael@0: michael@0: AddProperties(aAttributes, source); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::OpenProperty(const char16_t* aName, const char16_t** aAttributes) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // an "object" non-terminal is either a "description", a "typed michael@0: // node", or a "container", so this change the content sink's michael@0: // state appropriately. michael@0: nsCOMPtr localName; michael@0: const nsDependentSubstring& nameSpaceURI = michael@0: SplitExpatName(aName, getter_AddRefs(localName)); michael@0: michael@0: NS_ConvertUTF16toUTF8 propertyStr(nameSpaceURI); michael@0: propertyStr.Append(nsAtomCString(localName)); michael@0: michael@0: nsCOMPtr property; michael@0: rv = gRDFService->GetResource(propertyStr, getter_AddRefs(property)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // See if they've specified a 'resource' attribute, in which case michael@0: // they mean *that* to be the object of this property. michael@0: nsCOMPtr target; michael@0: GetResourceAttribute(aAttributes, getter_AddRefs(target)); michael@0: michael@0: bool isAnonymous = false; michael@0: michael@0: if (! target) { michael@0: // See if an 'ID' attribute has been specified, in which case michael@0: // this corresponds to the fourth form of [6.12]. michael@0: michael@0: // XXX strictly speaking, we should reject the RDF/XML as michael@0: // invalid if they've specified both an 'ID' and a 'resource' michael@0: // attribute. Bah. michael@0: michael@0: // XXX strictly speaking, 'about=' isn't allowed here, but michael@0: // what the hell. michael@0: GetIdAboutAttribute(aAttributes, getter_AddRefs(target), &isAnonymous); michael@0: } michael@0: michael@0: if (target) { michael@0: // They specified an inline resource for the value of this michael@0: // property. Create an RDF resource for the inline resource michael@0: // URI, add the properties to it, and attach the inline michael@0: // resource to its parent. michael@0: int32_t count; michael@0: rv = AddProperties(aAttributes, target, &count); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "problem adding properties"); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (count || !isAnonymous) { michael@0: // If the resource was "anonymous" (i.e., they hadn't michael@0: // explicitly set an ID or resource attribute), then we'll michael@0: // only assert this property from the context element *if* michael@0: // there were properties specified on the anonymous michael@0: // resource. michael@0: rv = mDataSource->Assert(GetContextElement(0), property, target, true); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: // XXX Technically, we should _not_ fall through here and push michael@0: // the element onto the stack: this is supposed to be a closed michael@0: // node. But right now I'm lazy and the code will just Do The michael@0: // Right Thing so long as the RDF is well-formed. michael@0: } michael@0: michael@0: // Push the element onto the context stack and change state. michael@0: PushContext(property, mState, mParseMode); michael@0: mState = eRDFContentSinkState_InPropertyElement; michael@0: SetParseMode(aAttributes); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::OpenMember(const char16_t* aName, michael@0: const char16_t** aAttributes) michael@0: { michael@0: // ensure that we're actually reading a member element by making michael@0: // sure that the opening tag is , where "rdf:" corresponds michael@0: // to whatever they've declared the standard RDF namespace to be. michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr localName; michael@0: const nsDependentSubstring& nameSpaceURI = michael@0: SplitExpatName(aName, getter_AddRefs(localName)); michael@0: michael@0: if (!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) || michael@0: localName != kLiAtom) { michael@0: PR_LOG(gLog, PR_LOG_ALWAYS, michael@0: ("rdfxml: expected RDF:li at line %d", michael@0: -1)); // XXX pass in line number michael@0: michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // The parent element is the container. michael@0: nsIRDFResource* container = GetContextElement(0); michael@0: if (! container) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: nsIRDFResource* resource; michael@0: if (NS_SUCCEEDED(rv = GetResourceAttribute(aAttributes, &resource))) { michael@0: // Okay, this node has an RDF:resource="..." attribute. That michael@0: // means that it's a "referenced item," as covered in [6.29]. michael@0: nsCOMPtr c; michael@0: NS_NewRDFContainer(getter_AddRefs(c)); michael@0: c->Init(mDataSource, container); michael@0: c->AppendElement(resource); michael@0: michael@0: // XXX Technically, we should _not_ fall through here and push michael@0: // the element onto the stack: this is supposed to be a closed michael@0: // node. But right now I'm lazy and the code will just Do The michael@0: // Right Thing so long as the RDF is well-formed. michael@0: NS_RELEASE(resource); michael@0: } michael@0: michael@0: // Change state. Pushing a null context element is a bit weird, michael@0: // but the idea is that there really is _no_ context "property". michael@0: // The contained element will use nsIRDFContainer::AppendElement() to add michael@0: // the element to the container, which requires only the container michael@0: // and the element to be added. michael@0: PushContext(nullptr, mState, mParseMode); michael@0: mState = eRDFContentSinkState_InMemberElement; michael@0: SetParseMode(aAttributes); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::OpenValue(const char16_t* aName, const char16_t** aAttributes) michael@0: { michael@0: // a "value" can either be an object or a string: we'll only get michael@0: // *here* if it's an object, as raw text is added as a leaf. michael@0: return OpenObject(aName,aAttributes); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // namespace resolution michael@0: void michael@0: RDFContentSinkImpl::RegisterNamespaces(const char16_t **aAttributes) michael@0: { michael@0: nsCOMPtr sink = do_QueryInterface(mDataSource); michael@0: if (!sink) { michael@0: return; michael@0: } michael@0: NS_NAMED_LITERAL_STRING(xmlns, "http://www.w3.org/2000/xmlns/"); michael@0: for (; *aAttributes; aAttributes += 2) { michael@0: // check the namespace michael@0: const char16_t* attr = aAttributes[0]; michael@0: const char16_t* xmlnsP = xmlns.BeginReading(); michael@0: while (*attr == *xmlnsP) { michael@0: ++attr; michael@0: ++xmlnsP; michael@0: } michael@0: if (*attr != 0xFFFF || michael@0: xmlnsP != xmlns.EndReading()) { michael@0: continue; michael@0: } michael@0: // get the localname (or "xmlns" for the default namespace) michael@0: const char16_t* endLocal = ++attr; michael@0: while (*endLocal && *endLocal != 0xFFFF) { michael@0: ++endLocal; michael@0: } michael@0: nsDependentSubstring lname(attr, endLocal); michael@0: nsCOMPtr preferred = do_GetAtom(lname); michael@0: if (preferred == kXMLNSAtom) { michael@0: preferred = nullptr; michael@0: } michael@0: sink->AddNameSpace(preferred, nsDependentString(aAttributes[1])); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // Qualified name resolution michael@0: michael@0: const nsDependentSubstring michael@0: RDFContentSinkImpl::SplitExpatName(const char16_t *aExpatName, michael@0: nsIAtom **aLocalName) michael@0: { michael@0: /** michael@0: * Expat can send the following: michael@0: * localName michael@0: * namespaceURIlocalName michael@0: * namespaceURIlocalNameprefix michael@0: * michael@0: * and we use 0xFFFF for the . michael@0: * michael@0: */ michael@0: michael@0: const char16_t *uriEnd = aExpatName; michael@0: const char16_t *nameStart = aExpatName; michael@0: const char16_t *pos; michael@0: for (pos = aExpatName; *pos; ++pos) { michael@0: if (*pos == 0xFFFF) { michael@0: if (uriEnd != aExpatName) { michael@0: break; michael@0: } michael@0: michael@0: uriEnd = pos; michael@0: nameStart = pos + 1; michael@0: } michael@0: } michael@0: michael@0: const nsDependentSubstring& nameSpaceURI = Substring(aExpatName, uriEnd); michael@0: *aLocalName = NS_NewAtom(Substring(nameStart, pos)).take(); michael@0: return nameSpaceURI; michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer) michael@0: { michael@0: // Do the right kind of initialization based on the container michael@0: // 'type' resource, and the state of the container (i.e., 'make' a michael@0: // new container vs. 'reinitialize' the container). michael@0: nsresult rv; michael@0: michael@0: static const ContainerInfo gContainerInfo[] = { michael@0: { &RDFContentSinkImpl::kRDF_Alt, &nsIRDFContainerUtils::IsAlt, &nsIRDFContainerUtils::MakeAlt }, michael@0: { &RDFContentSinkImpl::kRDF_Bag, &nsIRDFContainerUtils::IsBag, &nsIRDFContainerUtils::MakeBag }, michael@0: { &RDFContentSinkImpl::kRDF_Seq, &nsIRDFContainerUtils::IsSeq, &nsIRDFContainerUtils::MakeSeq }, michael@0: { 0, 0, 0 }, michael@0: }; michael@0: michael@0: for (const ContainerInfo* info = gContainerInfo; info->mType != 0; ++info) { michael@0: if (*info->mType != aContainerType) michael@0: continue; michael@0: michael@0: bool isContainer; michael@0: rv = (gRDFContainerUtils->*(info->mTestFn))(mDataSource, aContainer, &isContainer); michael@0: if (isContainer) { michael@0: rv = ReinitContainer(aContainerType, aContainer); michael@0: } michael@0: else { michael@0: rv = (gRDFContainerUtils->*(info->mMakeFn))(mDataSource, aContainer, nullptr); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_NOTREACHED("not an RDF container type"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer) michael@0: { michael@0: // Mega-kludge to deal with the fact that Make[Seq|Alt|Bag] is michael@0: // idempotent, and as such, containers will have state (e.g., michael@0: // RDF:nextVal) maintained in the graph across loads. This michael@0: // re-initializes each container's RDF:nextVal to '1', and 'marks' michael@0: // the container as such. michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr one; michael@0: rv = gRDFService->GetLiteral(MOZ_UTF16("1"), getter_AddRefs(one)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Re-initialize the 'nextval' property michael@0: nsCOMPtr nextval; michael@0: rv = mDataSource->GetTarget(aContainer, kRDF_nextVal, true, getter_AddRefs(nextval)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mDataSource->Change(aContainer, kRDF_nextVal, nextval, one); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Re-mark as a container. XXX should be kRDF_type michael@0: rv = mDataSource->Assert(aContainer, kRDF_instanceOf, aContainerType, true); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "unable to mark container as such"); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // Content stack management michael@0: michael@0: nsIRDFResource* michael@0: RDFContentSinkImpl::GetContextElement(int32_t ancestor /* = 0 */) michael@0: { michael@0: if ((nullptr == mContextStack) || michael@0: (uint32_t(ancestor) >= mContextStack->Length())) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return mContextStack->ElementAt( michael@0: mContextStack->Length()-ancestor-1).mResource; michael@0: } michael@0: michael@0: int32_t michael@0: RDFContentSinkImpl::PushContext(nsIRDFResource *aResource, michael@0: RDFContentSinkState aState, michael@0: RDFContentSinkParseMode aParseMode) michael@0: { michael@0: if (! mContextStack) { michael@0: mContextStack = new nsAutoTArray(); michael@0: if (! mContextStack) michael@0: return 0; michael@0: } michael@0: michael@0: RDFContextStackElement* e = mContextStack->AppendElement(); michael@0: if (! e) michael@0: return mContextStack->Length(); michael@0: michael@0: e->mResource = aResource; michael@0: e->mState = aState; michael@0: e->mParseMode = aParseMode; michael@0: michael@0: return mContextStack->Length(); michael@0: } michael@0: michael@0: nsresult michael@0: RDFContentSinkImpl::PopContext(nsIRDFResource *&aResource, michael@0: RDFContentSinkState &aState, michael@0: RDFContentSinkParseMode &aParseMode) michael@0: { michael@0: if ((nullptr == mContextStack) || michael@0: (mContextStack->IsEmpty())) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: uint32_t i = mContextStack->Length() - 1; michael@0: RDFContextStackElement &e = mContextStack->ElementAt(i); michael@0: michael@0: aResource = e.mResource; michael@0: NS_IF_ADDREF(aResource); michael@0: aState = e.mState; michael@0: aParseMode = e.mParseMode; michael@0: michael@0: mContextStack->RemoveElementAt(i); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: michael@0: nsresult michael@0: NS_NewRDFContentSink(nsIRDFContentSink** aResult) michael@0: { michael@0: NS_PRECONDITION(aResult != nullptr, "null ptr"); michael@0: if (! aResult) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: RDFContentSinkImpl* sink = new RDFContentSinkImpl(); michael@0: if (! sink) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(sink); michael@0: *aResult = sink; michael@0: return NS_OK; michael@0: }