1701 lines
52 KiB
C++
1701 lines
52 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.1 (the "License"); you may not use this file except in
|
|
* compliance with the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Pierre Phaneuf <pp@ludusdesign.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the NPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the NPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/*
|
|
|
|
An implementation for an NGLayout-style content sink that knows how
|
|
to build an RDF content model from XML-serialized RDF.
|
|
|
|
For more information on the RDF/XML syntax,
|
|
see http://www.w3.org/TR/REC-rdf-syntax/
|
|
|
|
This code is based on the final W3C Recommendation,
|
|
http://www.w3.org/TR/1999/REC-rdf-syntax-19990222.
|
|
|
|
Open Issues ------------------
|
|
|
|
1) factoring code with nsXMLContentSink - There's some amount of
|
|
common code between this and the HTML content sink. This will
|
|
increase as we support more and more HTML elements. How can code
|
|
from XML/HTML be factored?
|
|
|
|
2) We don't support the `parseType' attribute on the Description
|
|
tag; therefore, it is impossible to "inline" raw XML in this
|
|
implemenation.
|
|
|
|
3) We don't build the reifications at parse time due to the
|
|
footprint overhead it would incur for large RDF documents. (It
|
|
may be possible to attach a "reification" wrapper datasource that
|
|
would present this information at query-time.) Because of this,
|
|
the `bagID' attribute is not processed correctly.
|
|
|
|
4) No attempt is made to `resolve URIs' to a canonical form (the
|
|
specification hints that an implementation should do this). This
|
|
is omitted for the obvious reason that we can ill afford to
|
|
resolve each URI reference.
|
|
|
|
*/
|
|
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIContentSink.h"
|
|
#include "nsIRDFContainer.h"
|
|
#include "nsIRDFContainerUtils.h"
|
|
#include "nsIRDFContentSink.h"
|
|
#include "nsIRDFNode.h"
|
|
#include "nsIRDFService.h"
|
|
#include "nsIRDFXMLSink.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsIURL.h"
|
|
#include "nsIXMLContentSink.h"
|
|
#include "nsRDFCID.h"
|
|
#include "nsRDFParserUtils.h"
|
|
#include "nsVoidArray.h"
|
|
#include "nsXPIDLString.h"
|
|
#include "prlog.h"
|
|
#include "prmem.h"
|
|
#include "rdf.h"
|
|
#include "rdfutil.h"
|
|
#include "nsReadableUtils.h"
|
|
|
|
#include "nsHTMLTokens.h" // XXX so we can use nsIParserNode::GetTokenType()
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
static const char kNameSpaceSeparator = ':';
|
|
static const char kNameSpaceDef[] = "xmlns";
|
|
|
|
static const char kRDFNameSpaceURI[] = RDF_NAMESPACE_URI;
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// XPCOM IIDs
|
|
|
|
static NS_DEFINE_IID(kIContentSinkIID, NS_ICONTENT_SINK_IID); // XXX grr...
|
|
static NS_DEFINE_IID(kIRDFDataSourceIID, NS_IRDFDATASOURCE_IID);
|
|
static NS_DEFINE_IID(kIRDFServiceIID, NS_IRDFSERVICE_IID);
|
|
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
|
|
static NS_DEFINE_IID(kIXMLContentSinkIID, NS_IXMLCONTENT_SINK_IID);
|
|
static NS_DEFINE_IID(kIRDFContentSinkIID, NS_IRDFCONTENTSINK_IID);
|
|
|
|
static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
|
|
static NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
|
|
static NS_DEFINE_CID(kRDFInMemoryDataSourceCID, NS_RDFINMEMORYDATASOURCE_CID);
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef PR_LOGGING
|
|
static PRLogModuleInfo* gLog;
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Utility routines
|
|
|
|
// XXX This totally sucks. I wish that mozilla/base had this code.
|
|
static PRUnichar
|
|
rdf_EntityToUnicode(const char* buf)
|
|
{
|
|
if ((buf[0] == 'g' || buf[0] == 'G') &&
|
|
(buf[1] == 't' || buf[1] == 'T'))
|
|
return PRUnichar('>');
|
|
|
|
if ((buf[0] == 'l' || buf[0] == 'L') &&
|
|
(buf[1] == 't' || buf[1] == 'T'))
|
|
return PRUnichar('<');
|
|
|
|
if ((buf[0] == 'a' || buf[0] == 'A') &&
|
|
(buf[1] == 'm' || buf[1] == 'M') &&
|
|
(buf[2] == 'p' || buf[2] == 'P'))
|
|
return PRUnichar('&');
|
|
|
|
NS_NOTYETIMPLEMENTED("this is a named entity that I can't handle...");
|
|
return PRUnichar('?');
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
typedef enum {
|
|
eRDFContentSinkState_InProlog,
|
|
eRDFContentSinkState_InDocumentElement,
|
|
eRDFContentSinkState_InDescriptionElement,
|
|
eRDFContentSinkState_InContainerElement,
|
|
eRDFContentSinkState_InPropertyElement,
|
|
eRDFContentSinkState_InMemberElement,
|
|
eRDFContentSinkState_InEpilog
|
|
} RDFContentSinkState;
|
|
|
|
MOZ_DECL_CTOR_COUNTER(RDFContentSinkImpl::NameSpaceEntry)
|
|
|
|
class RDFContentSinkImpl : public nsIRDFContentSink
|
|
{
|
|
public:
|
|
RDFContentSinkImpl();
|
|
virtual ~RDFContentSinkImpl();
|
|
|
|
// nsISupports
|
|
NS_DECL_ISUPPORTS
|
|
|
|
// nsIContentSink
|
|
NS_IMETHOD WillBuildModel(void);
|
|
NS_IMETHOD DidBuildModel(PRInt32 aQualityLevel);
|
|
NS_IMETHOD WillInterrupt(void);
|
|
NS_IMETHOD WillResume(void);
|
|
NS_IMETHOD SetParser(nsIParser* aParser);
|
|
NS_IMETHOD OpenContainer(const nsIParserNode& aNode);
|
|
NS_IMETHOD CloseContainer(const nsIParserNode& aNode);
|
|
NS_IMETHOD AddLeaf(const nsIParserNode& aNode);
|
|
NS_IMETHOD AddComment(const nsIParserNode& aNode);
|
|
NS_IMETHOD AddProcessingInstruction(const nsIParserNode& aNode);
|
|
NS_IMETHOD NotifyError(const nsParserError* aError);
|
|
NS_IMETHOD AddDocTypeDecl(const nsIParserNode& aNode, PRInt32 aMode=0);
|
|
NS_IMETHOD FlushPendingNotifications() { return NS_OK; }
|
|
NS_IMETHOD SetDocumentCharset(nsAWritableString& aCharset) { return NS_OK; }
|
|
NS_IMETHOD NotifyTagObservers(nsIParserNode* aNode) { return NS_OK; }
|
|
|
|
// nsIXMLContentSink
|
|
NS_IMETHOD AddXMLDecl(const nsIParserNode& aNode);
|
|
NS_IMETHOD AddCharacterData(const nsIParserNode& aNode);
|
|
NS_IMETHOD AddUnparsedEntity(const nsIParserNode& aNode);
|
|
NS_IMETHOD AddNotation(const nsIParserNode& aNode);
|
|
NS_IMETHOD AddEntityReference(const nsIParserNode& aNode);
|
|
|
|
// nsIRDFContentSink
|
|
NS_IMETHOD Init(nsIURI* aURL);
|
|
NS_IMETHOD SetDataSource(nsIRDFDataSource* aDataSource);
|
|
NS_IMETHOD GetDataSource(nsIRDFDataSource*& aDataSource);
|
|
|
|
// pseudo constants
|
|
static PRInt32 gRefCnt;
|
|
static nsIRDFService* gRDFService;
|
|
static nsIRDFContainerUtils* gRDFContainerUtils;
|
|
static nsIRDFResource* kRDF_type;
|
|
static nsIRDFResource* kRDF_instanceOf; // XXX should be RDF:type
|
|
static nsIRDFResource* kRDF_Alt;
|
|
static nsIRDFResource* kRDF_Bag;
|
|
static nsIRDFResource* kRDF_Seq;
|
|
static nsIRDFResource* kRDF_nextVal;
|
|
|
|
static nsIAtom* kAboutAtom;
|
|
static nsIAtom* kIdAtom;
|
|
static nsIAtom* kAboutEachAtom;
|
|
static nsIAtom* kResourceAtom;
|
|
static nsIAtom* kRDFAtom;
|
|
static nsIAtom* kDescriptionAtom;
|
|
static nsIAtom* kBagAtom;
|
|
static nsIAtom* kSeqAtom;
|
|
static nsIAtom* kAltAtom;
|
|
static nsIAtom* kLiAtom;
|
|
static nsIAtom* kXMLNSAtom;
|
|
|
|
protected:
|
|
// Text management
|
|
nsresult FlushText(PRBool aCreateTextNode=PR_TRUE,
|
|
PRBool* aDidFlush=nsnull);
|
|
|
|
PRUnichar* mText;
|
|
PRInt32 mTextLength;
|
|
PRInt32 mTextSize;
|
|
PRBool mConstrainSize;
|
|
|
|
// namespace management
|
|
PRBool IsXMLNSDirective(const nsAReadableString& aAttributeKey, nsIAtom** aPrefix = nsnull);
|
|
nsresult PushNameSpacesFrom(const nsIParserNode& aNode);
|
|
nsresult PopNameSpaces();
|
|
|
|
struct NameSpaceEntry {
|
|
public:
|
|
NameSpaceEntry(nsIAtom* aPrefix, const char* aNameSpaceURI)
|
|
: mPrefix(aPrefix), mNext(nsnull) {
|
|
MOZ_COUNT_CTOR(RDFContentSinkImpl::NameSpaceEntry);
|
|
mNameSpaceURI = PL_strdup(aNameSpaceURI);
|
|
}
|
|
|
|
~NameSpaceEntry() {
|
|
MOZ_COUNT_DTOR(RDFContentSinkImpl::NameSpaceEntry);
|
|
PL_strfree(mNameSpaceURI);
|
|
}
|
|
|
|
nsCOMPtr<nsIAtom> mPrefix;
|
|
char* mNameSpaceURI;
|
|
NameSpaceEntry* mNext;
|
|
};
|
|
|
|
NameSpaceEntry* mNameSpaceStack;
|
|
nsAutoVoidArray mNameSpaceScopes;
|
|
|
|
nsIAtom*
|
|
CutNameSpacePrefix(nsString& aString);
|
|
|
|
nsresult
|
|
GetNameSpaceURI(nsIAtom* aPrefix, const char** aNameSpaceURI);
|
|
|
|
nsresult
|
|
ParseTagString(const nsAReadableString& aTagName,
|
|
const char** aNameSpaceURI,
|
|
nsIAtom** aTag);
|
|
|
|
nsresult
|
|
ParseAttributeString(const nsAReadableString& aAttributeName,
|
|
const char** aNameSpaceURI,
|
|
nsIAtom** aAttribute);
|
|
|
|
// RDF-specific parsing
|
|
nsresult GetIdAboutAttribute(const nsIParserNode& aNode, nsIRDFResource** aResource, PRBool* aIsAnonymous = nsnull);
|
|
nsresult GetResourceAttribute(const nsIParserNode& aNode, nsIRDFResource** aResource);
|
|
nsresult AddProperties(const nsIParserNode& aNode, nsIRDFResource* aSubject, PRInt32* aCount = nsnull);
|
|
|
|
enum eContainerType { eBag, eSeq, eAlt };
|
|
nsresult InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer);
|
|
nsresult ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer);
|
|
|
|
virtual nsresult OpenRDF(const nsIParserNode& aNode);
|
|
virtual nsresult OpenObject(const nsIParserNode& aNode);
|
|
virtual nsresult OpenProperty(const nsIParserNode& aNode);
|
|
virtual nsresult OpenMember(const nsIParserNode& aNode);
|
|
virtual nsresult OpenValue(const nsIParserNode& aNode);
|
|
|
|
// The datasource in which we're assigning assertions
|
|
nsCOMPtr<nsIRDFDataSource> mDataSource;
|
|
|
|
// The current state of the content sink
|
|
RDFContentSinkState mState;
|
|
|
|
// content stack management
|
|
PRInt32 PushContext(nsIRDFResource *aContext, RDFContentSinkState aState);
|
|
nsresult PopContext(nsIRDFResource*& rContext, RDFContentSinkState& rState);
|
|
nsIRDFResource* GetContextElement(PRInt32 ancestor = 0);
|
|
|
|
nsAutoVoidArray* mContextStack;
|
|
|
|
nsIURI* mDocumentURL;
|
|
};
|
|
|
|
PRInt32 RDFContentSinkImpl::gRefCnt = 0;
|
|
nsIRDFService* RDFContentSinkImpl::gRDFService;
|
|
nsIRDFContainerUtils* RDFContentSinkImpl::gRDFContainerUtils;
|
|
nsIRDFResource* RDFContentSinkImpl::kRDF_type;
|
|
nsIRDFResource* RDFContentSinkImpl::kRDF_instanceOf;
|
|
nsIRDFResource* RDFContentSinkImpl::kRDF_Alt;
|
|
nsIRDFResource* RDFContentSinkImpl::kRDF_Bag;
|
|
nsIRDFResource* RDFContentSinkImpl::kRDF_Seq;
|
|
nsIRDFResource* RDFContentSinkImpl::kRDF_nextVal;
|
|
|
|
nsIAtom* RDFContentSinkImpl::kAboutAtom;
|
|
nsIAtom* RDFContentSinkImpl::kIdAtom;
|
|
nsIAtom* RDFContentSinkImpl::kAboutEachAtom;
|
|
nsIAtom* RDFContentSinkImpl::kResourceAtom;
|
|
nsIAtom* RDFContentSinkImpl::kRDFAtom;
|
|
nsIAtom* RDFContentSinkImpl::kDescriptionAtom;
|
|
nsIAtom* RDFContentSinkImpl::kBagAtom;
|
|
nsIAtom* RDFContentSinkImpl::kSeqAtom;
|
|
nsIAtom* RDFContentSinkImpl::kAltAtom;
|
|
nsIAtom* RDFContentSinkImpl::kLiAtom;
|
|
nsIAtom* RDFContentSinkImpl::kXMLNSAtom;
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
RDFContentSinkImpl::RDFContentSinkImpl()
|
|
: mText(nsnull),
|
|
mTextLength(0),
|
|
mTextSize(0),
|
|
mConstrainSize(PR_TRUE),
|
|
mNameSpaceStack(nsnull),
|
|
mState(eRDFContentSinkState_InProlog),
|
|
mContextStack(nsnull),
|
|
mDocumentURL(nsnull)
|
|
{
|
|
NS_INIT_REFCNT();
|
|
|
|
if (gRefCnt++ == 0) {
|
|
nsresult rv;
|
|
rv = nsServiceManager::GetService(kRDFServiceCID,
|
|
kIRDFServiceIID,
|
|
(nsISupports**) &gRDFService);
|
|
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = gRDFService->GetResource(RDF_NAMESPACE_URI "type", &kRDF_type);
|
|
rv = gRDFService->GetResource(RDF_NAMESPACE_URI "instanceOf", &kRDF_instanceOf);
|
|
rv = gRDFService->GetResource(RDF_NAMESPACE_URI "Alt", &kRDF_Alt);
|
|
rv = gRDFService->GetResource(RDF_NAMESPACE_URI "Bag", &kRDF_Bag);
|
|
rv = gRDFService->GetResource(RDF_NAMESPACE_URI "Seq", &kRDF_Seq);
|
|
rv = gRDFService->GetResource(RDF_NAMESPACE_URI "nextVal", &kRDF_nextVal);
|
|
}
|
|
|
|
|
|
rv = nsServiceManager::GetService(kRDFContainerUtilsCID,
|
|
NS_GET_IID(nsIRDFContainerUtils),
|
|
(nsISupports**) &gRDFContainerUtils);
|
|
|
|
kAboutAtom = NS_NewAtom("about");
|
|
kIdAtom = NS_NewAtom("ID");
|
|
kAboutEachAtom = NS_NewAtom("aboutEach");
|
|
kResourceAtom = NS_NewAtom("resource");
|
|
kRDFAtom = NS_NewAtom("RDF");
|
|
kDescriptionAtom = NS_NewAtom("Description");
|
|
kBagAtom = NS_NewAtom("Bag");
|
|
kSeqAtom = NS_NewAtom("Seq");
|
|
kAltAtom = NS_NewAtom("Alt");
|
|
kLiAtom = NS_NewAtom("li");
|
|
kXMLNSAtom = NS_NewAtom("xmlns");
|
|
}
|
|
|
|
#ifdef PR_LOGGING
|
|
if (! gLog)
|
|
gLog = PR_NewLogModule("nsRDFContentSink");
|
|
#endif
|
|
}
|
|
|
|
|
|
RDFContentSinkImpl::~RDFContentSinkImpl()
|
|
{
|
|
#ifdef DEBUG_REFS
|
|
--gInstanceCount;
|
|
fprintf(stdout, "%d - RDF: RDFContentSinkImpl\n", gInstanceCount);
|
|
#endif
|
|
|
|
NS_IF_RELEASE(mDocumentURL);
|
|
|
|
if (mNameSpaceStack) {
|
|
// There shouldn't be any here except in an error condition
|
|
PRInt32 i = mNameSpaceScopes.Count();
|
|
while (--i >= 0)
|
|
PopNameSpaces();
|
|
}
|
|
if (mContextStack) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: warning! unclosed tag"));
|
|
|
|
// XXX we should never need to do this, but, we'll write the
|
|
// code all the same. If someone left the content stack dirty,
|
|
// pop all the elements off the stack and release them.
|
|
PRInt32 i = mContextStack->Count();
|
|
while (0 < i--) {
|
|
nsIRDFResource* resource;
|
|
RDFContentSinkState state;
|
|
PopContext(resource, state);
|
|
|
|
#ifdef PR_LOGGING
|
|
// print some fairly useless debugging info
|
|
// XXX we should save line numbers on the context stack: this'd
|
|
// be about 1000x more helpful.
|
|
if (resource) {
|
|
nsXPIDLCString uri;
|
|
resource->GetValue(getter_Copies(uri));
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: uri=%s", (const char*) uri));
|
|
}
|
|
#endif
|
|
|
|
NS_IF_RELEASE(resource);
|
|
}
|
|
|
|
delete mContextStack;
|
|
}
|
|
PR_FREEIF(mText);
|
|
|
|
|
|
if (--gRefCnt == 0) {
|
|
if (gRDFService) {
|
|
nsServiceManager::ReleaseService(kRDFServiceCID, gRDFService);
|
|
gRDFService = nsnull;
|
|
}
|
|
|
|
|
|
if (gRDFContainerUtils) {
|
|
nsServiceManager::ReleaseService(kRDFContainerUtilsCID, gRDFContainerUtils);
|
|
gRDFContainerUtils = nsnull;
|
|
}
|
|
|
|
NS_IF_RELEASE(kRDF_type);
|
|
NS_IF_RELEASE(kRDF_instanceOf);
|
|
NS_IF_RELEASE(kRDF_Alt);
|
|
NS_IF_RELEASE(kRDF_Bag);
|
|
NS_IF_RELEASE(kRDF_Seq);
|
|
NS_IF_RELEASE(kRDF_nextVal);
|
|
|
|
NS_IF_RELEASE(kAboutAtom);
|
|
NS_IF_RELEASE(kIdAtom);
|
|
NS_IF_RELEASE(kAboutEachAtom);
|
|
NS_IF_RELEASE(kResourceAtom);
|
|
NS_IF_RELEASE(kRDFAtom);
|
|
NS_IF_RELEASE(kDescriptionAtom);
|
|
NS_IF_RELEASE(kBagAtom);
|
|
NS_IF_RELEASE(kSeqAtom);
|
|
NS_IF_RELEASE(kAltAtom);
|
|
NS_IF_RELEASE(kLiAtom);
|
|
NS_IF_RELEASE(kXMLNSAtom);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// nsISupports interface
|
|
|
|
NS_IMPL_ADDREF(RDFContentSinkImpl);
|
|
NS_IMPL_RELEASE(RDFContentSinkImpl);
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::QueryInterface(REFNSIID iid, void** result)
|
|
{
|
|
NS_PRECONDITION(result, "null ptr");
|
|
if (! result)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
*result = nsnull;
|
|
if (iid.Equals(kIRDFContentSinkIID) ||
|
|
iid.Equals(kIXMLContentSinkIID) ||
|
|
iid.Equals(kIContentSinkIID) ||
|
|
iid.Equals(kISupportsIID)) {
|
|
*result = NS_STATIC_CAST(nsIXMLContentSink*, this);
|
|
AddRef();
|
|
return NS_OK;
|
|
}
|
|
return NS_NOINTERFACE;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// nsIContentSink interface
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::WillBuildModel(void)
|
|
{
|
|
if (mDataSource) {
|
|
nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
|
|
if (sink)
|
|
return sink->BeginLoad();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::DidBuildModel(PRInt32 aQualityLevel)
|
|
{
|
|
if (mDataSource) {
|
|
nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
|
|
if (sink)
|
|
return sink->EndLoad();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::WillInterrupt(void)
|
|
{
|
|
if (mDataSource) {
|
|
nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
|
|
if (sink)
|
|
return sink->Interrupt();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::WillResume(void)
|
|
{
|
|
if (mDataSource) {
|
|
nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
|
|
if (sink)
|
|
return sink->Resume();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::SetParser(nsIParser* aParser)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::OpenContainer(const nsIParserNode& aNode)
|
|
{
|
|
FlushText();
|
|
|
|
// We must register namespace declarations found in the attribute
|
|
// list of an element before creating the element. This is because
|
|
// the namespace prefix for an element might be declared within
|
|
// the attribute list.
|
|
PushNameSpacesFrom(aNode);
|
|
|
|
nsresult rv;
|
|
|
|
switch (mState) {
|
|
case eRDFContentSinkState_InProlog:
|
|
rv = OpenRDF(aNode);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InDocumentElement:
|
|
rv = OpenObject(aNode);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InDescriptionElement:
|
|
rv = OpenProperty(aNode);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InContainerElement:
|
|
rv = OpenMember(aNode);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InPropertyElement:
|
|
rv = OpenValue(aNode);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InMemberElement:
|
|
rv = OpenValue(aNode);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InEpilog:
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: unexpected content in epilog at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
rv = NS_ERROR_UNEXPECTED; // XXX
|
|
break;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::CloseContainer(const nsIParserNode& aNode)
|
|
{
|
|
FlushText();
|
|
|
|
nsIRDFResource* resource;
|
|
if (NS_FAILED(PopContext(resource, mState))) {
|
|
// XXX parser didn't catch unmatched tags?
|
|
#ifdef PR_LOGGING
|
|
if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
|
|
const nsAReadableString& tagStr = aNode.GetText();
|
|
char* tagCStr = ToNewCString(tagStr);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: extra close tag '%s' at line %d",
|
|
tagCStr, aNode.GetSourceLineNumber()));
|
|
|
|
nsCRT::free(tagCStr);
|
|
}
|
|
#endif
|
|
|
|
return NS_ERROR_UNEXPECTED; // XXX
|
|
}
|
|
|
|
// If we've just popped a member or property element, _now_ is the
|
|
// time to add that element to the graph.
|
|
switch (mState) {
|
|
case eRDFContentSinkState_InMemberElement: {
|
|
nsCOMPtr<nsIRDFContainer> container;
|
|
NS_NewRDFContainer(getter_AddRefs(container));
|
|
container->Init(mDataSource, GetContextElement(1));
|
|
container->AppendElement(resource);
|
|
} break;
|
|
|
|
case eRDFContentSinkState_InPropertyElement: {
|
|
mDataSource->Assert(GetContextElement(1), GetContextElement(0), resource, PR_TRUE);
|
|
} break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
PRInt32 nestLevel = mContextStack->Count();
|
|
if (nestLevel == 0)
|
|
mState = eRDFContentSinkState_InEpilog;
|
|
|
|
PopNameSpaces();
|
|
|
|
NS_IF_RELEASE(resource);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddLeaf(const nsIParserNode& aNode)
|
|
{
|
|
// XXX For now, all leaf content is character data
|
|
AddCharacterData(aNode);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::NotifyError(const nsParserError* aError)
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: %s", aError));
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIXMLContentSink
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddXMLDecl(const nsIParserNode& aNode)
|
|
{
|
|
// XXX We'll ignore it for now
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: ignoring XML decl at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddComment(const nsIParserNode& aNode)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddProcessingInstruction(const nsIParserNode& aNode)
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: ignoring processing instruction at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddDocTypeDecl(const nsIParserNode& aNode, PRInt32 aMode)
|
|
{
|
|
// XXX We'll ignore it for now
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: ignoring doc type decl at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddCharacterData(const nsIParserNode& aNode)
|
|
{
|
|
nsAutoString text;
|
|
|
|
if (aNode.GetTokenType() == eToken_entity) {
|
|
text = rdf_EntityToUnicode(
|
|
NS_LossyConvertUCS2toASCII(aNode.GetText()).get());
|
|
} else {
|
|
text = aNode.GetText();
|
|
}
|
|
|
|
PRInt32 addLen = text.Length();
|
|
if (0 == addLen) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Create buffer when we first need it
|
|
if (0 == mTextSize) {
|
|
mText = (PRUnichar *) PR_MALLOC(sizeof(PRUnichar) * 4096);
|
|
if (nsnull == mText) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
mTextSize = 4096;
|
|
}
|
|
|
|
// Copy data from string into our buffer; flush buffer when it fills up
|
|
PRInt32 offset = 0;
|
|
while (0 != addLen) {
|
|
PRInt32 amount = mTextSize - mTextLength;
|
|
if (amount > addLen) {
|
|
amount = addLen;
|
|
}
|
|
if (0 == amount) {
|
|
if (mConstrainSize) {
|
|
nsresult rv = FlushText();
|
|
if (NS_OK != rv) {
|
|
return rv;
|
|
}
|
|
}
|
|
else {
|
|
mTextSize += addLen;
|
|
mText = (PRUnichar *) PR_REALLOC(mText, sizeof(PRUnichar) * mTextSize);
|
|
if (nsnull == mText) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
}
|
|
memcpy(&mText[mTextLength], text.get() + offset,
|
|
sizeof(PRUnichar) * amount);
|
|
mTextLength += amount;
|
|
offset += amount;
|
|
addLen -= amount;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddUnparsedEntity(const nsIParserNode& aNode)
|
|
{
|
|
// XXX We'll ignore it for now
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: ignoring unparsed entity at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddNotation(const nsIParserNode& aNode)
|
|
{
|
|
// XXX We'll ignore it for now
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: ignoring notation at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::AddEntityReference(const nsIParserNode& aNode)
|
|
{
|
|
// XXX We'll ignore it for now
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: ignoring entity reference at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// nsIRDFContentSink interface
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::Init(nsIURI* aURL)
|
|
{
|
|
NS_PRECONDITION(aURL != nsnull, "null ptr");
|
|
if (! aURL)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
mDocumentURL = aURL;
|
|
NS_ADDREF(aURL);
|
|
|
|
mState = eRDFContentSinkState_InProlog;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::SetDataSource(nsIRDFDataSource* aDataSource)
|
|
{
|
|
mDataSource = dont_QueryInterface(aDataSource);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::GetDataSource(nsIRDFDataSource*& aDataSource)
|
|
{
|
|
aDataSource = mDataSource;
|
|
NS_IF_ADDREF(aDataSource);
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Text buffering
|
|
|
|
static PRBool
|
|
rdf_IsDataInBuffer(PRUnichar* buffer, PRInt32 length)
|
|
{
|
|
for (PRInt32 i = 0; i < length; ++i) {
|
|
if (buffer[i] == ' ' ||
|
|
buffer[i] == '\t' ||
|
|
buffer[i] == '\n' ||
|
|
buffer[i] == '\r')
|
|
continue;
|
|
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::FlushText(PRBool aCreateTextNode, PRBool* aDidFlush)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
PRBool didFlush = PR_FALSE;
|
|
if (0 != mTextLength) {
|
|
if (aCreateTextNode && rdf_IsDataInBuffer(mText, mTextLength)) {
|
|
// XXX if there's anything but whitespace, then we'll
|
|
// create a text node.
|
|
|
|
switch (mState) {
|
|
case eRDFContentSinkState_InMemberElement: {
|
|
nsAutoString value;
|
|
value.Append(mText, mTextLength);
|
|
value.Trim(" \t\n\r");
|
|
|
|
nsIRDFLiteral* literal;
|
|
if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(value.get(), &literal))) {
|
|
nsCOMPtr<nsIRDFContainer> container;
|
|
NS_NewRDFContainer(getter_AddRefs(container));
|
|
container->Init(mDataSource, GetContextElement(1));
|
|
container->AppendElement(literal);
|
|
NS_RELEASE(literal);
|
|
}
|
|
} break;
|
|
|
|
case eRDFContentSinkState_InPropertyElement: {
|
|
nsAutoString value;
|
|
value.Append(mText, mTextLength);
|
|
value.Trim(" \t\n\r");
|
|
|
|
nsCOMPtr<nsIRDFLiteral> target;
|
|
rv = gRDFService->GetLiteral(value.get(), getter_AddRefs(target));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = mDataSource->Assert(GetContextElement(1), GetContextElement(0), target, PR_TRUE);
|
|
if (NS_FAILED(rv)) return rv;
|
|
} break;
|
|
|
|
default:
|
|
// just ignore it
|
|
break;
|
|
}
|
|
}
|
|
mTextLength = 0;
|
|
didFlush = PR_TRUE;
|
|
}
|
|
if (nsnull != aDidFlush) {
|
|
*aDidFlush = didFlush;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Qualified name resolution
|
|
|
|
nsIAtom*
|
|
RDFContentSinkImpl::CutNameSpacePrefix(nsString& aString)
|
|
{
|
|
PRInt32 nsoffset = aString.FindChar(kNameSpaceSeparator);
|
|
|
|
if (nsoffset >= 0) {
|
|
nsAutoString prefix;
|
|
aString.Left(prefix, nsoffset);
|
|
aString.Cut(0, nsoffset+1);
|
|
return NS_NewAtom(prefix);
|
|
}
|
|
else {
|
|
return nsnull;
|
|
}
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::GetNameSpaceURI(nsIAtom* aPrefix, const char** aNameSpaceURI)
|
|
{
|
|
// If we're trying to resolve a namespace ID from a prefix, then
|
|
// we'd better have some namespaces open. We don't assert here
|
|
// because the likelihood of bogus files is high, and it doesn't
|
|
// make sense to drop into the debugger.
|
|
for (NameSpaceEntry* ns = mNameSpaceStack; ns != nsnull; ns = ns->mNext) {
|
|
if (ns->mPrefix.get() == aPrefix) {
|
|
*aNameSpaceURI = ns->mNameSpaceURI;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// Couldn't find the namespace, probably because the prefix
|
|
// was never declared using an 'xmlns' decl.
|
|
*aNameSpaceURI = nsnull;
|
|
|
|
#ifdef PR_LOGGING
|
|
if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
|
|
nsAutoString prefixStr;
|
|
if (aPrefix)
|
|
aPrefix->ToString(prefixStr);
|
|
|
|
char* prefixCStr = ToNewCString(prefixStr);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: undeclared namespace prefix '%s'",
|
|
prefixCStr));
|
|
|
|
nsCRT::free(prefixCStr);
|
|
}
|
|
#endif
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::ParseTagString(const nsAReadableString& aTagName,
|
|
const char** aNameSpaceURI,
|
|
nsIAtom** aTag)
|
|
{
|
|
// Split the fully-qualified name into a prefix and a tag part.
|
|
nsAutoString tag(aTagName);
|
|
nsCOMPtr<nsIAtom> prefix = getter_AddRefs(CutNameSpacePrefix(tag));
|
|
|
|
GetNameSpaceURI(prefix, aNameSpaceURI);
|
|
*aTag = NS_NewAtom(tag);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::ParseAttributeString(const nsAReadableString& aAttributeName,
|
|
const char** aNameSpaceURI,
|
|
nsIAtom** aAttribute)
|
|
{
|
|
// Split the fully-qualified name into a prefix and a tag part.
|
|
nsAutoString attr(aAttributeName);
|
|
nsCOMPtr<nsIAtom> prefix = getter_AddRefs(CutNameSpacePrefix(attr));
|
|
|
|
if (prefix) {
|
|
GetNameSpaceURI(prefix, aNameSpaceURI);
|
|
}
|
|
else {
|
|
*aNameSpaceURI = nsnull;
|
|
}
|
|
|
|
*aAttribute = NS_NewAtom(attr);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::GetIdAboutAttribute(const nsIParserNode& aNode,
|
|
nsIRDFResource** aResource,
|
|
PRBool* aIsAnonymous)
|
|
{
|
|
// This corresponds to the dirty work of production [6.5]
|
|
nsAutoString k;
|
|
PRInt32 ac = aNode.GetAttributeCount();
|
|
nsresult rv;
|
|
|
|
nsXPIDLCString docURI;
|
|
rv = mDocumentURL->GetSpec(getter_Copies(docURI));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
for (PRInt32 i = 0; i < ac; i++) {
|
|
// Get upper-cased key
|
|
const nsAReadableString& key = aNode.GetKeyAt(i);
|
|
|
|
const char* nameSpaceURI;
|
|
nsCOMPtr<nsIAtom> attr;
|
|
rv = ParseAttributeString(key, &nameSpaceURI, getter_AddRefs(attr));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// We'll accept either `ID' or `rdf:ID' (ibid with `about' or
|
|
// `rdf:about') in the spirit of being liberal towards the
|
|
// input that we receive.
|
|
if (nameSpaceURI && 0 != PL_strcmp(nameSpaceURI, kRDFNameSpaceURI))
|
|
continue;
|
|
|
|
// XXX you can't specify both, but we'll just pick up the
|
|
// first thing that was specified and ignore the other.
|
|
|
|
if (attr.get() == kAboutAtom) {
|
|
if (aIsAnonymous)
|
|
*aIsAnonymous = PR_FALSE;
|
|
|
|
nsAutoString uri(aNode.GetValueAt(i));
|
|
nsRDFParserUtils::StripAndConvert(uri);
|
|
|
|
rdf_MakeAbsoluteURI(NS_ConvertUTF8toUCS2(docURI), uri);
|
|
|
|
return gRDFService->GetUnicodeResource(uri.get(), aResource);
|
|
}
|
|
else if (attr.get() == kIdAtom) {
|
|
if (aIsAnonymous)
|
|
*aIsAnonymous = PR_FALSE;
|
|
|
|
nsAutoString name(aNode.GetValueAt(i));
|
|
nsRDFParserUtils::StripAndConvert(name);
|
|
|
|
// In the spirit of leniency, we do not bother trying to
|
|
// enforce that this be a valid "XML Name" (see
|
|
// http://www.w3.org/TR/REC-xml#NT-Nmtoken), as per
|
|
// 6.21. If we wanted to, this would be where to do it.
|
|
|
|
// Construct an in-line resource whose URI is the
|
|
// document's URI plus the XML name specified in the ID
|
|
// attribute.
|
|
name.Insert(PRUnichar('#'), 0);
|
|
|
|
rdf_MakeAbsoluteURI(NS_ConvertUTF8toUCS2(docURI), name);
|
|
|
|
return gRDFService->GetUnicodeResource(name.get(), aResource);
|
|
}
|
|
else if (attr.get() == kAboutEachAtom) {
|
|
// XXX we don't deal with aboutEach...
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: ignoring aboutEach at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
}
|
|
}
|
|
|
|
// Otherwise, we couldn't find anything, so just gensym one...
|
|
if (aIsAnonymous)
|
|
*aIsAnonymous = PR_TRUE;
|
|
|
|
rv = gRDFService->GetAnonymousResource(aResource);
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::GetResourceAttribute(const nsIParserNode& aNode,
|
|
nsIRDFResource** aResource)
|
|
{
|
|
nsresult rv;
|
|
nsAutoString k;
|
|
PRInt32 ac = aNode.GetAttributeCount();
|
|
|
|
for (PRInt32 i = 0; i < ac; i++) {
|
|
// Get upper-cased key
|
|
const nsAReadableString& key = aNode.GetKeyAt(i);
|
|
|
|
const char* nameSpaceURI;
|
|
nsCOMPtr<nsIAtom> attr;
|
|
rv = ParseAttributeString(key, &nameSpaceURI, getter_AddRefs(attr));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// We'll accept `resource' or `rdf:resource', under the spirit
|
|
// that we should be liberal towards the input that we
|
|
// receive.
|
|
if (nameSpaceURI && 0 != PL_strcmp(nameSpaceURI, kRDFNameSpaceURI))
|
|
continue;
|
|
|
|
// XXX you can't specify both, but we'll just pick up the
|
|
// first thing that was specified and ignore the other.
|
|
|
|
if (attr.get() == kResourceAtom) {
|
|
nsAutoString uri(aNode.GetValueAt(i));
|
|
nsRDFParserUtils::StripAndConvert(uri);
|
|
|
|
// XXX Take the URI and make it fully qualified by
|
|
// sticking it into the document's URL. This may not be
|
|
// appropriate...
|
|
char* documentURL;
|
|
mDocumentURL->GetSpec(&documentURL);
|
|
rdf_MakeAbsoluteURI(NS_ConvertUTF8toUCS2(documentURL), uri);
|
|
nsCRT::free(documentURL);
|
|
|
|
return gRDFService->GetUnicodeResource(uri.get(), aResource);
|
|
}
|
|
}
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::AddProperties(const nsIParserNode& aNode,
|
|
nsIRDFResource* aSubject,
|
|
PRInt32* aCount)
|
|
{
|
|
// Initialize the out parameter to zero, if they've asked for us
|
|
// to count properties we've added...
|
|
if (aCount)
|
|
*aCount = 0;
|
|
|
|
// Add tag attributes to the content attributes
|
|
PRInt32 count = aNode.GetAttributeCount();
|
|
|
|
for (PRInt32 i = 0; i < count; i++) {
|
|
// Get upper-cased key
|
|
const nsAReadableString& key = aNode.GetKeyAt(i);
|
|
|
|
// skip 'xmlns' directives, these are "meta" information
|
|
if (IsXMLNSDirective(key))
|
|
continue;
|
|
|
|
const char* nameSpaceURI;
|
|
nsCOMPtr<nsIAtom> attr;
|
|
ParseAttributeString(key, &nameSpaceURI, getter_AddRefs(attr));
|
|
|
|
// skip `about', `ID', and `resource' attributes (either with
|
|
// or without the `rdf:' prefix); these are all "special" and
|
|
// should've been dealt with by the caller.
|
|
if ((!nameSpaceURI || 0 == PL_strcmp(nameSpaceURI, kRDFNameSpaceURI)) &&
|
|
(attr.get() == kAboutAtom ||
|
|
attr.get() == kIdAtom ||
|
|
attr.get() == kResourceAtom))
|
|
continue;
|
|
|
|
nsAutoString v(aNode.GetValueAt(i));
|
|
nsRDFParserUtils::StripAndConvert(v);
|
|
|
|
nsCAutoString propertyStr(nameSpaceURI);
|
|
|
|
const PRUnichar* attrName;
|
|
attr->GetUnicode(&attrName);
|
|
|
|
propertyStr += NS_ConvertUCS2toUTF8(attrName);
|
|
|
|
// Add the assertion to RDF
|
|
nsCOMPtr<nsIRDFResource> property;
|
|
gRDFService->GetResource(propertyStr.get(), getter_AddRefs(property));
|
|
|
|
nsCOMPtr<nsIRDFLiteral> target;
|
|
gRDFService->GetLiteral(v.get(), getter_AddRefs(target));
|
|
|
|
mDataSource->Assert(aSubject, property, target, PR_TRUE);
|
|
|
|
// If the caller cares, let 'em know that we've added another
|
|
// property.
|
|
if (aCount)
|
|
++(*aCount);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX Wish there was a better macro in nsCom.h...
|
|
#if defined(XP_WIN)
|
|
#define STDCALL __stdcall
|
|
#elif defined(XP_OS2)
|
|
#define STDCALL
|
|
#else
|
|
#define STDCALL
|
|
#endif
|
|
|
|
typedef nsresult (STDCALL nsIRDFContainerUtils::*nsContainerTestFn)(nsIRDFDataSource* aDataSource,
|
|
nsIRDFResource* aResource,
|
|
PRBool* aResult);
|
|
|
|
typedef nsresult (STDCALL nsIRDFContainerUtils::*nsMakeContainerFn)(nsIRDFDataSource* aDataSource,
|
|
nsIRDFResource* aContainer,
|
|
nsIRDFContainer** aResult);
|
|
|
|
struct ContainerInfo {
|
|
nsIRDFResource** mType;
|
|
nsContainerTestFn mTestFn;
|
|
nsMakeContainerFn mMakeFn;
|
|
};
|
|
|
|
ContainerInfo gContainerInfo[] = {
|
|
{ &RDFContentSinkImpl::kRDF_Alt, &nsIRDFContainerUtils::IsAlt, &nsIRDFContainerUtils::MakeAlt },
|
|
{ &RDFContentSinkImpl::kRDF_Bag, &nsIRDFContainerUtils::IsBag, &nsIRDFContainerUtils::MakeBag },
|
|
{ &RDFContentSinkImpl::kRDF_Seq, &nsIRDFContainerUtils::IsSeq, &nsIRDFContainerUtils::MakeSeq },
|
|
{ 0, 0, 0 },
|
|
};
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer)
|
|
{
|
|
// Do the right kind of initialization based on the container
|
|
// 'type' resource, and the state of the container (i.e., 'make' a
|
|
// new container vs. 'reinitialize' the container).
|
|
nsresult rv;
|
|
|
|
for (ContainerInfo* info = gContainerInfo; info->mType != 0; ++info) {
|
|
if (*info->mType != aContainerType)
|
|
continue;
|
|
|
|
PRBool isContainer;
|
|
rv = (gRDFContainerUtils->*(info->mTestFn))(mDataSource, aContainer, &isContainer);
|
|
if (isContainer) {
|
|
rv = ReinitContainer(aContainerType, aContainer);
|
|
}
|
|
else {
|
|
rv = (gRDFContainerUtils->*(info->mMakeFn))(mDataSource, aContainer, nsnull);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_NOTREACHED("not an RDF container type");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer)
|
|
{
|
|
// Mega-kludge to deal with the fact that Make[Seq|Alt|Bag] is
|
|
// idempotent, and as such, containers will have state (e.g.,
|
|
// RDF:nextVal) maintained in the graph across loads. This
|
|
// re-initializes each container's RDF:nextVal to '1', and 'marks'
|
|
// the container as such.
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIRDFLiteral> one;
|
|
rv = gRDFService->GetLiteral(NS_LITERAL_STRING("1").get(), getter_AddRefs(one));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Re-initialize the 'nextval' property
|
|
nsCOMPtr<nsIRDFNode> nextval;
|
|
rv = mDataSource->GetTarget(aContainer, kRDF_nextVal, PR_TRUE, getter_AddRefs(nextval));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = mDataSource->Change(aContainer, kRDF_nextVal, nextval, one);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Re-mark as a container. XXX should be kRDF_type
|
|
rv = mDataSource->Assert(aContainer, kRDF_instanceOf, aContainerType, PR_TRUE);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to mark container as such");
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// RDF-specific routines used to build the model
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenRDF(const nsIParserNode& aNode)
|
|
{
|
|
// ensure that we're actually reading RDF by making sure that the
|
|
// opening tag is <rdf:RDF>, where "rdf:" corresponds to whatever
|
|
// they've declared the standard RDF namespace to be.
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIAtom> tag;
|
|
const char* nameSpaceURI;
|
|
rv = ParseTagString(aNode.GetText(), &nameSpaceURI, getter_AddRefs(tag));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if ((nameSpaceURI && 0 != PL_strcmp(nameSpaceURI, kRDFNameSpaceURI))
|
|
|| (tag.get() != kRDFAtom)) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: expected RDF:RDF at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
PushContext(nsnull, mState);
|
|
mState = eRDFContentSinkState_InDocumentElement;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenObject(const nsIParserNode& aNode)
|
|
{
|
|
// an "object" non-terminal is either a "description", a "typed
|
|
// node", or a "container", so this change the content sink's
|
|
// state appropriately.
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIAtom> tag;
|
|
const char* nameSpaceURI;
|
|
ParseTagString(aNode.GetText(), &nameSpaceURI, getter_AddRefs(tag));
|
|
|
|
// Figure out the URI of this object, and create an RDF node for it.
|
|
nsCOMPtr<nsIRDFResource> source;
|
|
GetIdAboutAttribute(aNode, getter_AddRefs(source));
|
|
|
|
// If there is no `ID' or `about', then there's not much we can do.
|
|
if (! source)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// Push the element onto the context stack
|
|
PushContext(source, mState);
|
|
|
|
// Now figure out what kind of state transition we need to
|
|
// make. We'll either be going into a mode where we parse a
|
|
// description or a container.
|
|
PRBool isaTypedNode = PR_TRUE;
|
|
|
|
if (nameSpaceURI && 0 == PL_strcmp(nameSpaceURI, kRDFNameSpaceURI)) {
|
|
isaTypedNode = PR_FALSE;
|
|
|
|
if (tag.get() == kDescriptionAtom) {
|
|
// it's a description
|
|
mState = eRDFContentSinkState_InDescriptionElement;
|
|
}
|
|
else if (tag.get() == kBagAtom) {
|
|
// it's a bag container
|
|
InitContainer(kRDF_Bag, source);
|
|
mState = eRDFContentSinkState_InContainerElement;
|
|
}
|
|
else if (tag.get() == kSeqAtom) {
|
|
// it's a seq container
|
|
InitContainer(kRDF_Seq, source);
|
|
mState = eRDFContentSinkState_InContainerElement;
|
|
}
|
|
else if (tag.get() == kAltAtom) {
|
|
// it's an alt container
|
|
InitContainer(kRDF_Alt, source);
|
|
mState = eRDFContentSinkState_InContainerElement;
|
|
}
|
|
else {
|
|
// heh, that's not *in* the RDF namespace: just treat it
|
|
// like a typed node
|
|
isaTypedNode = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
if (isaTypedNode) {
|
|
nsCAutoString typeStr;
|
|
|
|
if (nameSpaceURI)
|
|
typeStr = nameSpaceURI;
|
|
|
|
const PRUnichar *attrName;
|
|
tag->GetUnicode(&attrName);
|
|
|
|
typeStr += NS_ConvertUCS2toUTF8(attrName);
|
|
|
|
nsCOMPtr<nsIRDFResource> type;
|
|
rv = gRDFService->GetResource(typeStr.get(), getter_AddRefs(type));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = mDataSource->Assert(source, kRDF_type, type, PR_TRUE);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mState = eRDFContentSinkState_InDescriptionElement;
|
|
}
|
|
|
|
AddProperties(aNode, source);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenProperty(const nsIParserNode& aNode)
|
|
{
|
|
nsresult rv;
|
|
|
|
// an "object" non-terminal is either a "description", a "typed
|
|
// node", or a "container", so this change the content sink's
|
|
// state appropriately.
|
|
const char* nameSpaceURI;
|
|
nsCOMPtr<nsIAtom> tag;
|
|
ParseTagString(aNode.GetText(), &nameSpaceURI, getter_AddRefs(tag));
|
|
|
|
nsCAutoString propertyStr(nameSpaceURI);
|
|
|
|
const PRUnichar *attrName;
|
|
tag->GetUnicode(&attrName);
|
|
|
|
propertyStr += NS_ConvertUCS2toUTF8(attrName);
|
|
|
|
nsCOMPtr<nsIRDFResource> property;
|
|
rv = gRDFService->GetResource(propertyStr.get(), getter_AddRefs(property));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// See if they've specified a 'resource' attribute, in which case
|
|
// they mean *that* to be the object of this property.
|
|
nsCOMPtr<nsIRDFResource> target;
|
|
GetResourceAttribute(aNode, getter_AddRefs(target));
|
|
|
|
PRBool isAnonymous = PR_FALSE;
|
|
|
|
if (! target) {
|
|
// See if an 'ID' attribute has been specified, in which case
|
|
// this corresponds to the fourth form of [6.12].
|
|
|
|
// XXX strictly speaking, we should reject the RDF/XML as
|
|
// invalid if they've specified both an 'ID' and a 'resource'
|
|
// attribute. Bah.
|
|
|
|
// XXX strictly speaking, 'about=' isn't allowed here, but
|
|
// what the hell.
|
|
GetIdAboutAttribute(aNode, getter_AddRefs(target), &isAnonymous);
|
|
}
|
|
|
|
if (target) {
|
|
// They specified an inline resource for the value of this
|
|
// property. Create an RDF resource for the inline resource
|
|
// URI, add the properties to it, and attach the inline
|
|
// resource to its parent.
|
|
PRInt32 count;
|
|
rv = AddProperties(aNode, target, &count);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "problem adding properties");
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (count || !isAnonymous) {
|
|
// If the resource was "anonymous" (i.e., they hadn't
|
|
// explicitly set an ID or resource attribute), then we'll
|
|
// only assert this property from the context element *if*
|
|
// there were properties specified on the anonymous
|
|
// resource.
|
|
rv = mDataSource->Assert(GetContextElement(0), property, target, PR_TRUE);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// XXX Technically, we should _not_ fall through here and push
|
|
// the element onto the stack: this is supposed to be a closed
|
|
// node. But right now I'm lazy and the code will just Do The
|
|
// Right Thing so long as the RDF is well-formed.
|
|
}
|
|
|
|
// Push the element onto the context stack and change state.
|
|
PushContext(property, mState);
|
|
mState = eRDFContentSinkState_InPropertyElement;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenMember(const nsIParserNode& aNode)
|
|
{
|
|
// ensure that we're actually reading a member element by making
|
|
// sure that the opening tag is <rdf:li>, where "rdf:" corresponds
|
|
// to whatever they've declared the standard RDF namespace to be.
|
|
nsresult rv;
|
|
|
|
const char* nameSpaceURI;
|
|
nsCOMPtr<nsIAtom> tag;
|
|
ParseTagString(aNode.GetText(), &nameSpaceURI, getter_AddRefs(tag));
|
|
|
|
if ((0 != PL_strcmp(nameSpaceURI, kRDFNameSpaceURI)) || (tag.get() != kLiAtom)) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("rdfxml: expected RDF:li at line %d",
|
|
aNode.GetSourceLineNumber()));
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
// The parent element is the container.
|
|
nsIRDFResource* container = GetContextElement(0);
|
|
if (! container)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsIRDFResource* resource;
|
|
if (NS_SUCCEEDED(rv = GetResourceAttribute(aNode, &resource))) {
|
|
// Okay, this node has an RDF:resource="..." attribute. That
|
|
// means that it's a "referenced item," as covered in [6.29].
|
|
nsCOMPtr<nsIRDFContainer> c;
|
|
NS_NewRDFContainer(getter_AddRefs(c));
|
|
c->Init(mDataSource, container);
|
|
c->AppendElement(resource);
|
|
|
|
// XXX Technically, we should _not_ fall through here and push
|
|
// the element onto the stack: this is supposed to be a closed
|
|
// node. But right now I'm lazy and the code will just Do The
|
|
// Right Thing so long as the RDF is well-formed.
|
|
NS_RELEASE(resource);
|
|
}
|
|
|
|
// Change state. Pushing a null context element is a bit weird,
|
|
// but the idea is that there really is _no_ context "property".
|
|
// The contained element will use nsIRDFContainer::AppendElement() to add
|
|
// the element to the container, which requires only the container
|
|
// and the element to be added.
|
|
PushContext(nsnull, mState);
|
|
mState = eRDFContentSinkState_InMemberElement;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenValue(const nsIParserNode& aNode)
|
|
{
|
|
// a "value" can either be an object or a string: we'll only get
|
|
// *here* if it's an object, as raw text is added as a leaf.
|
|
return OpenObject(aNode);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Content stack management
|
|
|
|
struct RDFContextStackElement {
|
|
nsIRDFResource* mResource;
|
|
RDFContentSinkState mState;
|
|
};
|
|
|
|
nsIRDFResource*
|
|
RDFContentSinkImpl::GetContextElement(PRInt32 ancestor /* = 0 */)
|
|
{
|
|
if ((nsnull == mContextStack) ||
|
|
(ancestor >= mContextStack->Count())) {
|
|
return nsnull;
|
|
}
|
|
|
|
RDFContextStackElement* e =
|
|
NS_STATIC_CAST(RDFContextStackElement*, mContextStack->ElementAt(mContextStack->Count()-ancestor-1));
|
|
|
|
return e->mResource;
|
|
}
|
|
|
|
PRInt32
|
|
RDFContentSinkImpl::PushContext(nsIRDFResource *aResource, RDFContentSinkState aState)
|
|
{
|
|
if (! mContextStack) {
|
|
mContextStack = new nsAutoVoidArray();
|
|
if (! mContextStack)
|
|
return 0;
|
|
}
|
|
|
|
RDFContextStackElement* e = new RDFContextStackElement;
|
|
if (! e)
|
|
return mContextStack->Count();
|
|
|
|
NS_IF_ADDREF(aResource);
|
|
e->mResource = aResource;
|
|
e->mState = aState;
|
|
|
|
mContextStack->AppendElement(NS_STATIC_CAST(void*, e));
|
|
return mContextStack->Count();
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::PopContext(nsIRDFResource*& rResource, RDFContentSinkState& rState)
|
|
{
|
|
RDFContextStackElement* e;
|
|
if ((nsnull == mContextStack) ||
|
|
(0 == mContextStack->Count())) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
|
|
PRInt32 i = mContextStack->Count() - 1;
|
|
e = NS_STATIC_CAST(RDFContextStackElement*, mContextStack->ElementAt(i));
|
|
mContextStack->RemoveElementAt(i);
|
|
|
|
// don't bother Release()-ing: call it our implicit AddRef().
|
|
rResource = e->mResource;
|
|
rState = e->mState;
|
|
|
|
delete e;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Namespace management
|
|
|
|
PRBool
|
|
RDFContentSinkImpl::IsXMLNSDirective(const nsAReadableString& aAttributeKey, nsIAtom** aPrefix)
|
|
{
|
|
nsAutoString attr(aAttributeKey);
|
|
|
|
// Look for `xmlns' at the start of the attribute name
|
|
PRInt32 offset = attr.Find(kNameSpaceDef);
|
|
if (offset != 0)
|
|
return PR_FALSE;
|
|
|
|
PRInt32 prefixLen = attr.Length() - sizeof(kNameSpaceDef);
|
|
if (prefixLen <= 0) {
|
|
// they're setting the default namespace; leave `prefix'
|
|
// as nsnull.
|
|
}
|
|
else {
|
|
// make sure there's a `:' character
|
|
if (attr[sizeof(kNameSpaceDef) - 1] != kNameSpaceSeparator)
|
|
return PR_FALSE;
|
|
|
|
// if the caller wants the prefix back, compute it for them.
|
|
if (aPrefix) {
|
|
nsAutoString prefixStr;
|
|
attr.Right(prefixStr, prefixLen);
|
|
|
|
*aPrefix = NS_NewAtom(prefixStr);
|
|
}
|
|
}
|
|
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::PushNameSpacesFrom(const nsIParserNode& aNode)
|
|
{
|
|
// Remember the current top of the stack as the namespace
|
|
// scope. When popping namespaces, we'll remove stack elements
|
|
// until we hit this.
|
|
mNameSpaceScopes.AppendElement(mNameSpaceStack);
|
|
|
|
PRInt32 count = aNode.GetAttributeCount();
|
|
for (PRInt32 i = 0; i < count; ++i) {
|
|
const nsAReadableString& key = aNode.GetKeyAt(i);
|
|
|
|
nsCOMPtr<nsIAtom> prefix;
|
|
if (! IsXMLNSDirective(key, getter_AddRefs(prefix)))
|
|
continue;
|
|
|
|
nsAutoString uri(aNode.GetValueAt(i));
|
|
nsRDFParserUtils::StripAndConvert(uri);
|
|
|
|
// Open a local namespace
|
|
NameSpaceEntry* ns = new NameSpaceEntry(prefix, NS_ConvertUCS2toUTF8(uri).get());
|
|
if (! ns)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
ns->mNext = mNameSpaceStack;
|
|
mNameSpaceStack = ns;
|
|
|
|
// Add it to the set of namespaces used in the RDF/XML document.
|
|
nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
|
|
if (sink)
|
|
sink->AddNameSpace(prefix, uri);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::PopNameSpaces()
|
|
{
|
|
// Close a namespace scope by removing the topmost entries from
|
|
// the namespace stack.
|
|
PRInt32 top = mNameSpaceScopes.Count() - 1;
|
|
if (top < 0)
|
|
return NS_ERROR_UNEXPECTED; // XXX huh?
|
|
|
|
NameSpaceEntry* ns = NS_STATIC_CAST(NameSpaceEntry*, mNameSpaceScopes[top]);
|
|
mNameSpaceScopes.RemoveElementAt(top);
|
|
|
|
while (mNameSpaceStack && mNameSpaceStack != ns) {
|
|
NameSpaceEntry* doomed = mNameSpaceStack;
|
|
mNameSpaceStack = mNameSpaceStack->mNext;
|
|
delete doomed;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
nsresult
|
|
NS_NewRDFContentSink(nsIRDFContentSink** aResult)
|
|
{
|
|
NS_PRECONDITION(aResult != nsnull, "null ptr");
|
|
if (! aResult)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
RDFContentSinkImpl* sink = new RDFContentSinkImpl();
|
|
if (! sink)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(sink);
|
|
*aResult = sink;
|
|
return NS_OK;
|
|
}
|