зеркало из https://github.com/mozilla/gecko-dev.git
1468 строки
46 KiB
C++
1468 строки
46 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/*
|
|
|
|
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 "nsInterfaceHashtable.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 "nsTArray.h"
|
|
#include "nsXPIDLString.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "rdf.h"
|
|
#include "rdfutil.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsIExpatSink.h"
|
|
#include "nsCRT.h"
|
|
#include "nsIAtom.h"
|
|
#include "nsStaticAtom.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIDTD.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
enum RDFContentSinkState {
|
|
eRDFContentSinkState_InProlog,
|
|
eRDFContentSinkState_InDocumentElement,
|
|
eRDFContentSinkState_InDescriptionElement,
|
|
eRDFContentSinkState_InContainerElement,
|
|
eRDFContentSinkState_InPropertyElement,
|
|
eRDFContentSinkState_InMemberElement,
|
|
eRDFContentSinkState_InEpilog
|
|
};
|
|
|
|
enum RDFContentSinkParseMode {
|
|
eRDFContentSinkParseMode_Resource,
|
|
eRDFContentSinkParseMode_Literal,
|
|
eRDFContentSinkParseMode_Int,
|
|
eRDFContentSinkParseMode_Date
|
|
};
|
|
|
|
typedef decltype(&nsIRDFContainerUtils::IsAlt) nsContainerTestFn;
|
|
typedef decltype(&nsIRDFContainerUtils::MakeAlt) nsMakeContainerFn;
|
|
|
|
class RDFContentSinkImpl : public nsIRDFContentSink,
|
|
public nsIExpatSink
|
|
{
|
|
public:
|
|
RDFContentSinkImpl();
|
|
|
|
// nsISupports
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIEXPATSINK
|
|
|
|
// nsIContentSink
|
|
NS_IMETHOD WillParse(void) override;
|
|
NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override;
|
|
NS_IMETHOD DidBuildModel(bool aTerminated) override;
|
|
NS_IMETHOD WillInterrupt(void) override;
|
|
NS_IMETHOD WillResume(void) override;
|
|
NS_IMETHOD SetParser(nsParserBase* aParser) override;
|
|
virtual void FlushPendingNotifications(mozilla::FlushType aType) override { }
|
|
NS_IMETHOD SetDocumentCharset(nsACString& aCharset) override { return NS_OK; }
|
|
virtual nsISupports *GetTarget() override { return nullptr; }
|
|
|
|
// nsIRDFContentSink
|
|
NS_IMETHOD Init(nsIURI* aURL) override;
|
|
NS_IMETHOD SetDataSource(nsIRDFDataSource* aDataSource) override;
|
|
NS_IMETHOD GetDataSource(nsIRDFDataSource*& aDataSource) override;
|
|
|
|
// pseudo constants
|
|
static int32_t 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;
|
|
|
|
#define RDF_ATOM(name_, value_) static nsIAtom* name_;
|
|
#include "nsRDFContentSinkAtomList.h"
|
|
#undef RDF_ATOM
|
|
|
|
typedef struct ContainerInfo {
|
|
nsIRDFResource** mType;
|
|
nsContainerTestFn mTestFn;
|
|
nsMakeContainerFn mMakeFn;
|
|
} ContainerInfo;
|
|
|
|
protected:
|
|
virtual ~RDFContentSinkImpl();
|
|
|
|
// Text management
|
|
void ParseText(nsIRDFNode **aResult);
|
|
|
|
nsresult FlushText();
|
|
nsresult AddText(const char16_t* aText, int32_t aLength);
|
|
|
|
// RDF-specific parsing
|
|
nsresult OpenRDF(const char16_t* aName);
|
|
nsresult OpenObject(const char16_t* aName ,const char16_t** aAttributes);
|
|
nsresult OpenProperty(const char16_t* aName, const char16_t** aAttributes);
|
|
nsresult OpenMember(const char16_t* aName, const char16_t** aAttributes);
|
|
nsresult OpenValue(const char16_t* aName, const char16_t** aAttributes);
|
|
|
|
nsresult GetIdAboutAttribute(const char16_t** aAttributes, nsIRDFResource** aResource, bool* aIsAnonymous = nullptr);
|
|
nsresult GetResourceAttribute(const char16_t** aAttributes, nsIRDFResource** aResource);
|
|
nsresult AddProperties(const char16_t** aAttributes, nsIRDFResource* aSubject, int32_t* aCount = nullptr);
|
|
void SetParseMode(const char16_t **aAttributes);
|
|
|
|
char16_t* mText;
|
|
int32_t mTextLength;
|
|
int32_t mTextSize;
|
|
|
|
/**
|
|
* From the set of given attributes, this method extracts the
|
|
* namespace definitions and feeds them to the datasource.
|
|
* These can then be suggested to the serializer to be used again.
|
|
* Hopefully, this will keep namespace definitions intact in a
|
|
* parse - serialize cycle.
|
|
*/
|
|
void RegisterNamespaces(const char16_t **aAttributes);
|
|
|
|
/**
|
|
* Extracts the localname from aExpatName, the name that the Expat parser
|
|
* passes us.
|
|
* aLocalName will contain the localname in aExpatName.
|
|
* The return value is a dependent string containing just the namespace.
|
|
*/
|
|
const nsDependentSubstring SplitExpatName(const char16_t *aExpatName,
|
|
nsIAtom **aLocalName);
|
|
|
|
enum eContainerType { eBag, eSeq, eAlt };
|
|
nsresult InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer);
|
|
nsresult ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer);
|
|
|
|
// The datasource in which we're assigning assertions
|
|
nsCOMPtr<nsIRDFDataSource> mDataSource;
|
|
|
|
// A hash of all the node IDs referred to
|
|
nsInterfaceHashtable<nsStringHashKey, nsIRDFResource> mNodeIDMap;
|
|
|
|
// The current state of the content sink
|
|
RDFContentSinkState mState;
|
|
RDFContentSinkParseMode mParseMode;
|
|
|
|
// content stack management
|
|
int32_t
|
|
PushContext(nsIRDFResource *aContext,
|
|
RDFContentSinkState aState,
|
|
RDFContentSinkParseMode aParseMode);
|
|
|
|
nsresult
|
|
PopContext(nsIRDFResource *&aContext,
|
|
RDFContentSinkState &aState,
|
|
RDFContentSinkParseMode &aParseMode);
|
|
|
|
nsIRDFResource* GetContextElement(int32_t ancestor = 0);
|
|
|
|
|
|
struct RDFContextStackElement {
|
|
nsCOMPtr<nsIRDFResource> mResource;
|
|
RDFContentSinkState mState;
|
|
RDFContentSinkParseMode mParseMode;
|
|
};
|
|
|
|
AutoTArray<RDFContextStackElement, 8>* mContextStack;
|
|
|
|
nsCOMPtr<nsIURI> mDocumentURL;
|
|
|
|
private:
|
|
static mozilla::LazyLogModule gLog;
|
|
};
|
|
|
|
int32_t 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;
|
|
|
|
mozilla::LazyLogModule RDFContentSinkImpl::gLog("nsRDFContentSink");
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
#define RDF_ATOM(name_, value_) nsIAtom* RDFContentSinkImpl::name_;
|
|
#include "nsRDFContentSinkAtomList.h"
|
|
#undef RDF_ATOM
|
|
|
|
#define RDF_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
|
|
#include "nsRDFContentSinkAtomList.h"
|
|
#undef RDF_ATOM
|
|
|
|
static const nsStaticAtom rdf_atoms[] = {
|
|
#define RDF_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &RDFContentSinkImpl::name_),
|
|
#include "nsRDFContentSinkAtomList.h"
|
|
#undef RDF_ATOM
|
|
};
|
|
|
|
// static
|
|
void
|
|
nsRDFAtoms::RegisterAtoms()
|
|
{
|
|
NS_RegisterStaticAtoms(rdf_atoms);
|
|
}
|
|
|
|
RDFContentSinkImpl::RDFContentSinkImpl()
|
|
: mText(nullptr),
|
|
mTextLength(0),
|
|
mTextSize(0),
|
|
mState(eRDFContentSinkState_InProlog),
|
|
mParseMode(eRDFContentSinkParseMode_Literal),
|
|
mContextStack(nullptr)
|
|
{
|
|
if (gRefCnt++ == 0) {
|
|
NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
|
|
nsresult rv = CallGetService(kRDFServiceCID, &gRDFService);
|
|
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"),
|
|
&kRDF_type);
|
|
rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
|
|
&kRDF_instanceOf);
|
|
rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"),
|
|
&kRDF_Alt);
|
|
rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"),
|
|
&kRDF_Bag);
|
|
rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"),
|
|
&kRDF_Seq);
|
|
rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"),
|
|
&kRDF_nextVal);
|
|
}
|
|
|
|
NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
|
|
rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
|
|
}
|
|
}
|
|
|
|
|
|
RDFContentSinkImpl::~RDFContentSinkImpl()
|
|
{
|
|
#ifdef DEBUG_REFS
|
|
--gInstanceCount;
|
|
fprintf(stdout, "%d - RDF: RDFContentSinkImpl\n", gInstanceCount);
|
|
#endif
|
|
|
|
if (mContextStack) {
|
|
MOZ_LOG(gLog, LogLevel::Warning,
|
|
("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.
|
|
int32_t i = mContextStack->Length();
|
|
while (0 < i--) {
|
|
nsIRDFResource* resource = nullptr;
|
|
RDFContentSinkState state;
|
|
RDFContentSinkParseMode parseMode;
|
|
PopContext(resource, state, parseMode);
|
|
|
|
// 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 && MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
|
|
nsXPIDLCString uri;
|
|
resource->GetValue(getter_Copies(uri));
|
|
MOZ_LOG(gLog, LogLevel::Debug,
|
|
("rdfxml: uri=%s", (const char*) uri));
|
|
}
|
|
|
|
NS_IF_RELEASE(resource);
|
|
}
|
|
|
|
delete mContextStack;
|
|
}
|
|
free(mText);
|
|
|
|
|
|
if (--gRefCnt == 0) {
|
|
NS_IF_RELEASE(gRDFService);
|
|
NS_IF_RELEASE(gRDFContainerUtils);
|
|
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);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// 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;
|
|
|
|
NS_DEFINE_IID(kIContentSinkIID, NS_ICONTENT_SINK_IID);
|
|
NS_DEFINE_IID(kIExpatSinkIID, NS_IEXPATSINK_IID);
|
|
NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
|
|
NS_DEFINE_IID(kIXMLContentSinkIID, NS_IXMLCONTENT_SINK_IID);
|
|
NS_DEFINE_IID(kIRDFContentSinkIID, NS_IRDFCONTENTSINK_IID);
|
|
|
|
*result = nullptr;
|
|
if (iid.Equals(kIRDFContentSinkIID) ||
|
|
iid.Equals(kIXMLContentSinkIID) ||
|
|
iid.Equals(kIContentSinkIID) ||
|
|
iid.Equals(kISupportsIID)) {
|
|
*result = static_cast<nsIXMLContentSink*>(this);
|
|
AddRef();
|
|
return NS_OK;
|
|
}
|
|
else if (iid.Equals(kIExpatSinkIID)) {
|
|
*result = static_cast<nsIExpatSink*>(this);
|
|
AddRef();
|
|
return NS_OK;
|
|
}
|
|
return NS_NOINTERFACE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::HandleStartElement(const char16_t *aName,
|
|
const char16_t **aAtts,
|
|
uint32_t aAttsCount,
|
|
uint32_t aLineNumber)
|
|
{
|
|
FlushText();
|
|
|
|
nsresult rv = NS_ERROR_UNEXPECTED; // XXX
|
|
|
|
RegisterNamespaces(aAtts);
|
|
|
|
switch (mState) {
|
|
case eRDFContentSinkState_InProlog:
|
|
rv = OpenRDF(aName);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InDocumentElement:
|
|
rv = OpenObject(aName,aAtts);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InDescriptionElement:
|
|
rv = OpenProperty(aName,aAtts);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InContainerElement:
|
|
rv = OpenMember(aName,aAtts);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InPropertyElement:
|
|
case eRDFContentSinkState_InMemberElement:
|
|
rv = OpenValue(aName,aAtts);
|
|
break;
|
|
|
|
case eRDFContentSinkState_InEpilog:
|
|
MOZ_LOG(gLog, LogLevel::Warning,
|
|
("rdfxml: unexpected content in epilog at line %d",
|
|
aLineNumber));
|
|
break;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::HandleEndElement(const char16_t *aName)
|
|
{
|
|
FlushText();
|
|
|
|
nsIRDFResource* resource;
|
|
if (NS_FAILED(PopContext(resource, mState, mParseMode))) {
|
|
// XXX parser didn't catch unmatched tags?
|
|
if (MOZ_LOG_TEST(gLog, LogLevel::Warning)) {
|
|
nsAutoString tagStr(aName);
|
|
char* tagCStr = ToNewCString(tagStr);
|
|
|
|
MOZ_LOG(gLog, LogLevel::Warning,
|
|
("rdfxml: extra close tag '%s' at line %d",
|
|
tagCStr, 0/*XXX fix me */));
|
|
|
|
free(tagCStr);
|
|
}
|
|
|
|
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, true);
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (mContextStack->IsEmpty())
|
|
mState = eRDFContentSinkState_InEpilog;
|
|
|
|
NS_IF_RELEASE(resource);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::HandleComment(const char16_t *aName)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::HandleCDataSection(const char16_t *aData,
|
|
uint32_t aLength)
|
|
{
|
|
return aData ? AddText(aData, aLength) : NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::HandleDoctypeDecl(const nsAString & aSubset,
|
|
const nsAString & aName,
|
|
const nsAString & aSystemId,
|
|
const nsAString & aPublicId,
|
|
nsISupports* aCatalogData)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::HandleCharacterData(const char16_t *aData,
|
|
uint32_t aLength)
|
|
{
|
|
return aData ? AddText(aData, aLength) : NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::HandleProcessingInstruction(const char16_t *aTarget,
|
|
const char16_t *aData)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::HandleXMLDeclaration(const char16_t *aVersion,
|
|
const char16_t *aEncoding,
|
|
int32_t aStandalone)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::ReportError(const char16_t* aErrorText,
|
|
const char16_t* aSourceText,
|
|
nsIScriptError *aError,
|
|
bool *_retval)
|
|
{
|
|
NS_PRECONDITION(aError && aSourceText && aErrorText, "Check arguments!!!");
|
|
|
|
// The expat driver should report the error.
|
|
*_retval = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// nsIContentSink interface
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::WillParse(void)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::WillBuildModel(nsDTDMode)
|
|
{
|
|
if (mDataSource) {
|
|
nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
|
|
if (sink)
|
|
return sink->BeginLoad();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::DidBuildModel(bool aTerminated)
|
|
{
|
|
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(nsParserBase* aParser)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// nsIRDFContentSink interface
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::Init(nsIURI* aURL)
|
|
{
|
|
NS_PRECONDITION(aURL != nullptr, "null ptr");
|
|
if (! aURL)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
mDocumentURL = aURL;
|
|
mState = eRDFContentSinkState_InProlog;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::SetDataSource(nsIRDFDataSource* aDataSource)
|
|
{
|
|
NS_PRECONDITION(aDataSource != nullptr, "SetDataSource null ptr");
|
|
mDataSource = aDataSource;
|
|
NS_ASSERTION(mDataSource != nullptr,"Couldn't QI RDF DataSource");
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
RDFContentSinkImpl::GetDataSource(nsIRDFDataSource*& aDataSource)
|
|
{
|
|
aDataSource = mDataSource;
|
|
NS_IF_ADDREF(aDataSource);
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Text buffering
|
|
|
|
static bool
|
|
rdf_IsDataInBuffer(char16_t* buffer, int32_t length)
|
|
{
|
|
for (int32_t i = 0; i < length; ++i) {
|
|
if (buffer[i] == ' ' ||
|
|
buffer[i] == '\t' ||
|
|
buffer[i] == '\n' ||
|
|
buffer[i] == '\r')
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
RDFContentSinkImpl::ParseText(nsIRDFNode **aResult)
|
|
{
|
|
// XXXwaterson wasteful, but we'd need to make a copy anyway to be
|
|
// able to call nsIRDFService::Get[Resource|Literal|...]().
|
|
nsAutoString value;
|
|
value.Append(mText, mTextLength);
|
|
value.Trim(" \t\n\r");
|
|
|
|
switch (mParseMode) {
|
|
case eRDFContentSinkParseMode_Literal:
|
|
{
|
|
nsIRDFLiteral *result;
|
|
gRDFService->GetLiteral(value.get(), &result);
|
|
*aResult = result;
|
|
}
|
|
break;
|
|
|
|
case eRDFContentSinkParseMode_Resource:
|
|
{
|
|
nsIRDFResource *result;
|
|
gRDFService->GetUnicodeResource(value, &result);
|
|
*aResult = result;
|
|
}
|
|
break;
|
|
|
|
case eRDFContentSinkParseMode_Int:
|
|
{
|
|
nsresult err;
|
|
int32_t i = value.ToInteger(&err);
|
|
nsIRDFInt *result;
|
|
gRDFService->GetIntLiteral(i, &result);
|
|
*aResult = result;
|
|
}
|
|
break;
|
|
|
|
case eRDFContentSinkParseMode_Date:
|
|
{
|
|
PRTime t = rdf_ParseDate(nsDependentCString(NS_LossyConvertUTF16toASCII(value).get(), value.Length()));
|
|
nsIRDFDate *result;
|
|
gRDFService->GetDateLiteral(t, &result);
|
|
*aResult = result;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
NS_NOTREACHED("unknown parse type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::FlushText()
|
|
{
|
|
nsresult rv = NS_OK;
|
|
if (0 != mTextLength) {
|
|
if (rdf_IsDataInBuffer(mText, mTextLength)) {
|
|
// XXX if there's anything but whitespace, then we'll
|
|
// create a text node.
|
|
|
|
switch (mState) {
|
|
case eRDFContentSinkState_InMemberElement: {
|
|
nsCOMPtr<nsIRDFNode> node;
|
|
ParseText(getter_AddRefs(node));
|
|
|
|
nsCOMPtr<nsIRDFContainer> container;
|
|
NS_NewRDFContainer(getter_AddRefs(container));
|
|
container->Init(mDataSource, GetContextElement(1));
|
|
|
|
container->AppendElement(node);
|
|
} break;
|
|
|
|
case eRDFContentSinkState_InPropertyElement: {
|
|
nsCOMPtr<nsIRDFNode> node;
|
|
ParseText(getter_AddRefs(node));
|
|
|
|
mDataSource->Assert(GetContextElement(1), GetContextElement(0), node, true);
|
|
} break;
|
|
|
|
default:
|
|
// just ignore it
|
|
break;
|
|
}
|
|
}
|
|
mTextLength = 0;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::AddText(const char16_t* aText, int32_t aLength)
|
|
{
|
|
// Create buffer when we first need it
|
|
if (0 == mTextSize) {
|
|
mText = (char16_t *) malloc(sizeof(char16_t) * 4096);
|
|
if (!mText) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
mTextSize = 4096;
|
|
}
|
|
|
|
// Copy data from string into our buffer; grow the buffer as needed.
|
|
// It never shrinks, but since the content sink doesn't stick around,
|
|
// this shouldn't be a bloat issue.
|
|
int32_t amount = mTextSize - mTextLength;
|
|
if (amount < aLength) {
|
|
// Grow the buffer by at least a factor of two to prevent thrashing.
|
|
// Since PR_REALLOC will leave mText intact if the call fails,
|
|
// don't clobber mText or mTextSize until the new mem is allocated.
|
|
int32_t newSize = (2 * mTextSize > (mTextSize + aLength)) ?
|
|
(2 * mTextSize) : (mTextSize + aLength);
|
|
char16_t* newText =
|
|
(char16_t *) realloc(mText, sizeof(char16_t) * newSize);
|
|
if (!newText)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
mTextSize = newSize;
|
|
mText = newText;
|
|
}
|
|
memcpy(&mText[mTextLength], aText, sizeof(char16_t) * aLength);
|
|
mTextLength += aLength;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
rdf_RequiresAbsoluteURI(const nsString& uri)
|
|
{
|
|
// cheap shot at figuring out if this requires an absolute url translation
|
|
return !(StringBeginsWith(uri, NS_LITERAL_STRING("urn:")) ||
|
|
StringBeginsWith(uri, NS_LITERAL_STRING("chrome:")));
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::GetIdAboutAttribute(const char16_t** aAttributes,
|
|
nsIRDFResource** aResource,
|
|
bool* aIsAnonymous)
|
|
{
|
|
// This corresponds to the dirty work of production [6.5]
|
|
nsresult rv = NS_OK;
|
|
|
|
nsAutoString nodeID;
|
|
|
|
nsCOMPtr<nsIAtom> localName;
|
|
for (; *aAttributes; aAttributes += 2) {
|
|
const nsDependentSubstring& nameSpaceURI =
|
|
SplitExpatName(aAttributes[0], getter_AddRefs(localName));
|
|
|
|
// 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.IsEmpty() &&
|
|
!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) {
|
|
continue;
|
|
}
|
|
|
|
// XXX you can't specify both, but we'll just pick up the
|
|
// first thing that was specified and ignore the other.
|
|
|
|
if (localName == kAboutAtom) {
|
|
if (aIsAnonymous)
|
|
*aIsAnonymous = false;
|
|
|
|
nsAutoString relURI(aAttributes[1]);
|
|
if (rdf_RequiresAbsoluteURI(relURI)) {
|
|
nsAutoCString uri;
|
|
rv = mDocumentURL->Resolve(NS_ConvertUTF16toUTF8(aAttributes[1]), uri);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return gRDFService->GetResource(uri,
|
|
aResource);
|
|
}
|
|
return gRDFService->GetResource(NS_ConvertUTF16toUTF8(aAttributes[1]),
|
|
aResource);
|
|
}
|
|
else if (localName == kIdAtom) {
|
|
if (aIsAnonymous)
|
|
*aIsAnonymous = false;
|
|
// 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.
|
|
nsAutoCString name;
|
|
nsAutoCString ref('#');
|
|
AppendUTF16toUTF8(aAttributes[1], ref);
|
|
|
|
rv = mDocumentURL->Resolve(ref, name);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return gRDFService->GetResource(name, aResource);
|
|
}
|
|
else if (localName == kNodeIdAtom) {
|
|
nodeID.Assign(aAttributes[1]);
|
|
}
|
|
else if (localName == kAboutEachAtom) {
|
|
// XXX we don't deal with aboutEach...
|
|
//MOZ_LOG(gLog, LogLevel::Warning,
|
|
// ("rdfxml: ignoring aboutEach at line %d",
|
|
// aNode.GetSourceLineNumber()));
|
|
}
|
|
}
|
|
|
|
// Otherwise, we couldn't find anything, so just gensym one...
|
|
if (aIsAnonymous)
|
|
*aIsAnonymous = true;
|
|
|
|
// If nodeID is present, check if we already know about it. If we've seen
|
|
// the nodeID before, use the same resource, otherwise generate a new one.
|
|
if (!nodeID.IsEmpty()) {
|
|
mNodeIDMap.Get(nodeID,aResource);
|
|
|
|
if (!*aResource) {
|
|
rv = gRDFService->GetAnonymousResource(aResource);
|
|
mNodeIDMap.Put(nodeID,*aResource);
|
|
}
|
|
}
|
|
else {
|
|
rv = gRDFService->GetAnonymousResource(aResource);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::GetResourceAttribute(const char16_t** aAttributes,
|
|
nsIRDFResource** aResource)
|
|
{
|
|
nsCOMPtr<nsIAtom> localName;
|
|
|
|
nsAutoString nodeID;
|
|
|
|
for (; *aAttributes; aAttributes += 2) {
|
|
const nsDependentSubstring& nameSpaceURI =
|
|
SplitExpatName(aAttributes[0], getter_AddRefs(localName));
|
|
|
|
// We'll accept `resource' or `rdf:resource', under the spirit
|
|
// that we should be liberal towards the input that we
|
|
// receive.
|
|
if (!nameSpaceURI.IsEmpty() &&
|
|
!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) {
|
|
continue;
|
|
}
|
|
|
|
// XXX you can't specify both, but we'll just pick up the
|
|
// first thing that was specified and ignore the other.
|
|
|
|
if (localName == kResourceAtom) {
|
|
// XXX Take the URI and make it fully qualified by
|
|
// sticking it into the document's URL. This may not be
|
|
// appropriate...
|
|
nsAutoString relURI(aAttributes[1]);
|
|
if (rdf_RequiresAbsoluteURI(relURI)) {
|
|
nsresult rv;
|
|
nsAutoCString uri;
|
|
|
|
rv = mDocumentURL->Resolve(NS_ConvertUTF16toUTF8(aAttributes[1]), uri);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return gRDFService->GetResource(uri, aResource);
|
|
}
|
|
return gRDFService->GetResource(NS_ConvertUTF16toUTF8(aAttributes[1]),
|
|
aResource);
|
|
}
|
|
else if (localName == kNodeIdAtom) {
|
|
nodeID.Assign(aAttributes[1]);
|
|
}
|
|
}
|
|
|
|
// If nodeID is present, check if we already know about it. If we've seen
|
|
// the nodeID before, use the same resource, otherwise generate a new one.
|
|
if (!nodeID.IsEmpty()) {
|
|
mNodeIDMap.Get(nodeID,aResource);
|
|
|
|
if (!*aResource) {
|
|
nsresult rv;
|
|
rv = gRDFService->GetAnonymousResource(aResource);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
mNodeIDMap.Put(nodeID,*aResource);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::AddProperties(const char16_t** aAttributes,
|
|
nsIRDFResource* aSubject,
|
|
int32_t* aCount)
|
|
{
|
|
if (aCount)
|
|
*aCount = 0;
|
|
|
|
nsCOMPtr<nsIAtom> localName;
|
|
for (; *aAttributes; aAttributes += 2) {
|
|
const nsDependentSubstring& nameSpaceURI =
|
|
SplitExpatName(aAttributes[0], getter_AddRefs(localName));
|
|
|
|
// skip 'xmlns' directives, these are "meta" information
|
|
if (nameSpaceURI.EqualsLiteral("http://www.w3.org/2000/xmlns/")) {
|
|
continue;
|
|
}
|
|
|
|
// skip `about', `ID', `resource', and 'nodeID' attributes (either with or
|
|
// without the `rdf:' prefix); these are all "special" and
|
|
// should've been dealt with by the caller.
|
|
if (localName == kAboutAtom || localName == kIdAtom ||
|
|
localName == kResourceAtom || localName == kNodeIdAtom) {
|
|
if (nameSpaceURI.IsEmpty() ||
|
|
nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI))
|
|
continue;
|
|
}
|
|
|
|
// Skip `parseType', `RDF:parseType', and `NC:parseType'. This
|
|
// is meta-information that will be handled in SetParseMode.
|
|
if (localName == kParseTypeAtom) {
|
|
if (nameSpaceURI.IsEmpty() ||
|
|
nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) ||
|
|
nameSpaceURI.EqualsLiteral(NC_NAMESPACE_URI)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 propertyStr(nameSpaceURI);
|
|
propertyStr.Append(nsAtomCString(localName));
|
|
|
|
// Add the assertion to RDF
|
|
nsCOMPtr<nsIRDFResource> property;
|
|
gRDFService->GetResource(propertyStr, getter_AddRefs(property));
|
|
|
|
nsCOMPtr<nsIRDFLiteral> target;
|
|
gRDFService->GetLiteral(aAttributes[1],
|
|
getter_AddRefs(target));
|
|
|
|
mDataSource->Assert(aSubject, property, target, true);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
RDFContentSinkImpl::SetParseMode(const char16_t **aAttributes)
|
|
{
|
|
nsCOMPtr<nsIAtom> localName;
|
|
for (; *aAttributes; aAttributes += 2) {
|
|
const nsDependentSubstring& nameSpaceURI =
|
|
SplitExpatName(aAttributes[0], getter_AddRefs(localName));
|
|
|
|
if (localName == kParseTypeAtom) {
|
|
nsDependentString v(aAttributes[1]);
|
|
|
|
if (nameSpaceURI.IsEmpty() ||
|
|
nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) {
|
|
if (v.EqualsLiteral("Resource"))
|
|
mParseMode = eRDFContentSinkParseMode_Resource;
|
|
|
|
break;
|
|
}
|
|
else if (nameSpaceURI.EqualsLiteral(NC_NAMESPACE_URI)) {
|
|
if (v.EqualsLiteral("Date"))
|
|
mParseMode = eRDFContentSinkParseMode_Date;
|
|
else if (v.EqualsLiteral("Integer"))
|
|
mParseMode = eRDFContentSinkParseMode_Int;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// RDF-specific routines used to build the model
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenRDF(const char16_t* aName)
|
|
{
|
|
// 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.
|
|
nsCOMPtr<nsIAtom> localName;
|
|
const nsDependentSubstring& nameSpaceURI =
|
|
SplitExpatName(aName, getter_AddRefs(localName));
|
|
|
|
if (!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) || localName != kRDFAtom) {
|
|
// MOZ_LOG(gLog, LogLevel::Info,
|
|
// ("rdfxml: expected RDF:RDF at line %d",
|
|
// aNode.GetSourceLineNumber()));
|
|
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
PushContext(nullptr, mState, mParseMode);
|
|
mState = eRDFContentSinkState_InDocumentElement;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenObject(const char16_t* aName,
|
|
const char16_t** aAttributes)
|
|
{
|
|
// an "object" non-terminal is either a "description", a "typed
|
|
// node", or a "container", so this change the content sink's
|
|
// state appropriately.
|
|
nsCOMPtr<nsIAtom> localName;
|
|
const nsDependentSubstring& nameSpaceURI =
|
|
SplitExpatName(aName, getter_AddRefs(localName));
|
|
|
|
// Figure out the URI of this object, and create an RDF node for it.
|
|
nsCOMPtr<nsIRDFResource> source;
|
|
GetIdAboutAttribute(aAttributes, 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, mParseMode);
|
|
|
|
// 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.
|
|
bool isaTypedNode = true;
|
|
|
|
if (nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) {
|
|
isaTypedNode = false;
|
|
|
|
if (localName == kDescriptionAtom) {
|
|
// it's a description
|
|
mState = eRDFContentSinkState_InDescriptionElement;
|
|
}
|
|
else if (localName == kBagAtom) {
|
|
// it's a bag container
|
|
InitContainer(kRDF_Bag, source);
|
|
mState = eRDFContentSinkState_InContainerElement;
|
|
}
|
|
else if (localName == kSeqAtom) {
|
|
// it's a seq container
|
|
InitContainer(kRDF_Seq, source);
|
|
mState = eRDFContentSinkState_InContainerElement;
|
|
}
|
|
else if (localName == 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 = true;
|
|
}
|
|
}
|
|
|
|
if (isaTypedNode) {
|
|
NS_ConvertUTF16toUTF8 typeStr(nameSpaceURI);
|
|
typeStr.Append(nsAtomCString(localName));
|
|
|
|
nsCOMPtr<nsIRDFResource> type;
|
|
nsresult rv = gRDFService->GetResource(typeStr, getter_AddRefs(type));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = mDataSource->Assert(source, kRDF_type, type, true);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mState = eRDFContentSinkState_InDescriptionElement;
|
|
}
|
|
|
|
AddProperties(aAttributes, source);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenProperty(const char16_t* aName, const char16_t** aAttributes)
|
|
{
|
|
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.
|
|
nsCOMPtr<nsIAtom> localName;
|
|
const nsDependentSubstring& nameSpaceURI =
|
|
SplitExpatName(aName, getter_AddRefs(localName));
|
|
|
|
NS_ConvertUTF16toUTF8 propertyStr(nameSpaceURI);
|
|
propertyStr.Append(nsAtomCString(localName));
|
|
|
|
nsCOMPtr<nsIRDFResource> property;
|
|
rv = gRDFService->GetResource(propertyStr, 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(aAttributes, getter_AddRefs(target));
|
|
|
|
bool isAnonymous = 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(aAttributes, 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.
|
|
int32_t count;
|
|
rv = AddProperties(aAttributes, 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, 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, mParseMode);
|
|
mState = eRDFContentSinkState_InPropertyElement;
|
|
SetParseMode(aAttributes);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenMember(const char16_t* aName,
|
|
const char16_t** aAttributes)
|
|
{
|
|
// 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;
|
|
|
|
nsCOMPtr<nsIAtom> localName;
|
|
const nsDependentSubstring& nameSpaceURI =
|
|
SplitExpatName(aName, getter_AddRefs(localName));
|
|
|
|
if (!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) ||
|
|
localName != kLiAtom) {
|
|
MOZ_LOG(gLog, LogLevel::Error,
|
|
("rdfxml: expected RDF:li at line %d",
|
|
-1)); // XXX pass in line number
|
|
|
|
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(aAttributes, &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(nullptr, mState, mParseMode);
|
|
mState = eRDFContentSinkState_InMemberElement;
|
|
SetParseMode(aAttributes);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::OpenValue(const char16_t* aName, const char16_t** aAttributes)
|
|
{
|
|
// 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(aName,aAttributes);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// namespace resolution
|
|
void
|
|
RDFContentSinkImpl::RegisterNamespaces(const char16_t **aAttributes)
|
|
{
|
|
nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
|
|
if (!sink) {
|
|
return;
|
|
}
|
|
NS_NAMED_LITERAL_STRING(xmlns, "http://www.w3.org/2000/xmlns/");
|
|
for (; *aAttributes; aAttributes += 2) {
|
|
// check the namespace
|
|
const char16_t* attr = aAttributes[0];
|
|
const char16_t* xmlnsP = xmlns.BeginReading();
|
|
while (*attr == *xmlnsP) {
|
|
++attr;
|
|
++xmlnsP;
|
|
}
|
|
if (*attr != 0xFFFF ||
|
|
xmlnsP != xmlns.EndReading()) {
|
|
continue;
|
|
}
|
|
// get the localname (or "xmlns" for the default namespace)
|
|
const char16_t* endLocal = ++attr;
|
|
while (*endLocal && *endLocal != 0xFFFF) {
|
|
++endLocal;
|
|
}
|
|
nsDependentSubstring lname(attr, endLocal);
|
|
nsCOMPtr<nsIAtom> preferred = NS_Atomize(lname);
|
|
if (preferred == kXMLNSAtom) {
|
|
preferred = nullptr;
|
|
}
|
|
sink->AddNameSpace(preferred, nsDependentString(aAttributes[1]));
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Qualified name resolution
|
|
|
|
const nsDependentSubstring
|
|
RDFContentSinkImpl::SplitExpatName(const char16_t *aExpatName,
|
|
nsIAtom **aLocalName)
|
|
{
|
|
/**
|
|
* Expat can send the following:
|
|
* localName
|
|
* namespaceURI<separator>localName
|
|
* namespaceURI<separator>localName<separator>prefix
|
|
*
|
|
* and we use 0xFFFF for the <separator>.
|
|
*
|
|
*/
|
|
|
|
const char16_t *uriEnd = aExpatName;
|
|
const char16_t *nameStart = aExpatName;
|
|
const char16_t *pos;
|
|
for (pos = aExpatName; *pos; ++pos) {
|
|
if (*pos == 0xFFFF) {
|
|
if (uriEnd != aExpatName) {
|
|
break;
|
|
}
|
|
|
|
uriEnd = pos;
|
|
nameStart = pos + 1;
|
|
}
|
|
}
|
|
|
|
const nsDependentSubstring& nameSpaceURI = Substring(aExpatName, uriEnd);
|
|
*aLocalName = NS_Atomize(Substring(nameStart, pos)).take();
|
|
return nameSpaceURI;
|
|
}
|
|
|
|
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;
|
|
|
|
static const 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 },
|
|
};
|
|
|
|
for (const ContainerInfo* info = gContainerInfo; info->mType != 0; ++info) {
|
|
if (*info->mType != aContainerType)
|
|
continue;
|
|
|
|
bool isContainer;
|
|
rv = (gRDFContainerUtils->*(info->mTestFn))(mDataSource, aContainer, &isContainer);
|
|
if (isContainer) {
|
|
rv = ReinitContainer(aContainerType, aContainer);
|
|
}
|
|
else {
|
|
rv = (gRDFContainerUtils->*(info->mMakeFn))(mDataSource, aContainer, nullptr);
|
|
}
|
|
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(u"1", getter_AddRefs(one));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Re-initialize the 'nextval' property
|
|
nsCOMPtr<nsIRDFNode> nextval;
|
|
rv = mDataSource->GetTarget(aContainer, kRDF_nextVal, 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, true);
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to mark container as such");
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Content stack management
|
|
|
|
nsIRDFResource*
|
|
RDFContentSinkImpl::GetContextElement(int32_t ancestor /* = 0 */)
|
|
{
|
|
if ((nullptr == mContextStack) ||
|
|
(uint32_t(ancestor) >= mContextStack->Length())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return mContextStack->ElementAt(
|
|
mContextStack->Length()-ancestor-1).mResource;
|
|
}
|
|
|
|
int32_t
|
|
RDFContentSinkImpl::PushContext(nsIRDFResource *aResource,
|
|
RDFContentSinkState aState,
|
|
RDFContentSinkParseMode aParseMode)
|
|
{
|
|
if (! mContextStack) {
|
|
mContextStack = new AutoTArray<RDFContextStackElement, 8>();
|
|
if (! mContextStack)
|
|
return 0;
|
|
}
|
|
|
|
RDFContextStackElement* e = mContextStack->AppendElement();
|
|
if (! e)
|
|
return mContextStack->Length();
|
|
|
|
e->mResource = aResource;
|
|
e->mState = aState;
|
|
e->mParseMode = aParseMode;
|
|
|
|
return mContextStack->Length();
|
|
}
|
|
|
|
nsresult
|
|
RDFContentSinkImpl::PopContext(nsIRDFResource *&aResource,
|
|
RDFContentSinkState &aState,
|
|
RDFContentSinkParseMode &aParseMode)
|
|
{
|
|
if ((nullptr == mContextStack) ||
|
|
(mContextStack->IsEmpty())) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
|
|
uint32_t i = mContextStack->Length() - 1;
|
|
RDFContextStackElement &e = mContextStack->ElementAt(i);
|
|
|
|
aResource = e.mResource;
|
|
NS_IF_ADDREF(aResource);
|
|
aState = e.mState;
|
|
aParseMode = e.mParseMode;
|
|
|
|
mContextStack->RemoveElementAt(i);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
nsresult
|
|
NS_NewRDFContentSink(nsIRDFContentSink** aResult)
|
|
{
|
|
NS_PRECONDITION(aResult != nullptr, "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;
|
|
}
|