Bug 336858. feed content preview not linkified with Atom feeds. Handle Atom links, xml:base for relative references, and 11 unit tests. r=ben.

This commit is contained in:
sayrer%gmail.com 2006-07-11 16:49:00 +00:00
Родитель 14e94e07d2
Коммит 7a3bb8d6b3
13 изменённых файлов: 384 добавлений и 5 удалений

Просмотреть файл

@ -54,6 +54,11 @@ interface nsIFeedContainer : nsISupports
*/ */
attribute AString identifier; attribute AString identifier;
/**
* The baseURI for the Entry or Feed.
*/
attribute nsIURI baseURI;
/** /**
* The fields found in the document. Common Atom * The fields found in the document. Common Atom
* and RSS fields are normalized. This includes some namespaced * and RSS fields are normalized. This includes some namespaced

Просмотреть файл

@ -54,6 +54,8 @@ var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
var gUnescapeHTML = Cc[UNESCAPE_CONTRACTID]. var gUnescapeHTML = Cc[UNESCAPE_CONTRACTID].
getService(Ci.nsIScriptableUnescapeHTML); getService(Ci.nsIScriptableUnescapeHTML);
const XMLNS = "http://www.w3.org/XML/1998/namespace";
/***** Some general utils *****/ /***** Some general utils *****/
function strToURI(link, base) { function strToURI(link, base) {
var base = base || null; var base = base || null;
@ -120,6 +122,33 @@ function plainTextFromTextConstruct(textConstruct) {
return textConstruct; return textConstruct;
} }
/**
* Searches through an array of links and returns a JS array
* of matching property bags.
*/
const IANA_URI = "http://www.iana.org/assignments/relation/";
function findAtomLinks(rel, links) {
var rvLinks = [];
for (var i = 0; i < links.length; ++i) {
var linkElement = links.queryElementAt(i, Ci.nsIPropertyBag2);
// atom:link MUST have @href
if (bagHasKey(linkElement, "href")) {
var relAttribute = null;
if (bagHasKey(linkElement, "rel"))
relAttribute = linkElement.getPropertyAsAString("rel")
if ((!relAttribute && rel == "alternate") || relAttribute == rel) {
rvLinks.push(linkElement);
continue;
}
// catch relations specified by IANA URI
if (relAttribute == IANA_URI + rel) {
rvLinks.push(linkElement);
}
}
}
return rvLinks;
}
function xmlEscape(s) { function xmlEscape(s) {
s = s.replace(/&/g, "&amp;"); s = s.replace(/&/g, "&amp;");
s = s.replace(/>/g, "&gt;"); s = s.replace(/>/g, "&gt;");
@ -138,6 +167,17 @@ function arrayContains(array, element) {
return false; return false;
} }
// XXX add hasKey to nsIPropertyBag
function bagHasKey(bag, key) {
try {
bag.getProperty(key);
return true;
}
catch (e) {
return false;
}
}
/** /**
* XXX Thunderbird's W3C-DTF function * XXX Thunderbird's W3C-DTF function
* *
@ -235,7 +275,8 @@ var gNamespaces = {
"http://www.w3.org/1999/02/22-rdf-syntax-ns#":"rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#":"rdf",
"http://purl.org/rss/1.0/":"rss1", "http://purl.org/rss/1.0/":"rss1",
"http://wellformedweb.org/CommentAPI/":"wfw", "http://wellformedweb.org/CommentAPI/":"wfw",
"http://purl.org/rss/1.0/modules/wiki/":"wiki" "http://purl.org/rss/1.0/modules/wiki/":"wiki",
"http://www.w3.org/XML/1998/namespace":"xml"
} }
// lets us know to ignore extraneous attributes like // lets us know to ignore extraneous attributes like
@ -284,6 +325,7 @@ function Feed() {
this._title = null; this._title = null;
this.items = []; this.items = [];
this.link = null; this.link = null;
this.baseURI = null;
} }
Feed.prototype = { Feed.prototype = {
subtitle: function Feed_subtitle(doStripTags) { subtitle: function Feed_subtitle(doStripTags) {
@ -319,11 +361,33 @@ Feed.prototype = {
normalize: function Feed_normalize() { normalize: function Feed_normalize() {
fieldsToObj(this, this.searchLists); fieldsToObj(this, this.searchLists);
if (this.skipDays) { if (this.skipDays)
this.skipDays = this.skipDays.getProperty("days"); this.skipDays = this.skipDays.getProperty("days");
} if (this.skipHours)
if (this.skipHours) {
this.skipHours = this.skipHours.getProperty("hours"); this.skipHours = this.skipHours.getProperty("hours");
// Assign Atom link if needed
if (bagHasKey(this.fields, "links"))
this._atomLinksToURI();
},
_atomLinksToURI: function Feed_linkToURI() {
var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
var alternates = findAtomLinks("alternate", links);
if (alternates.length > 0) {
try {
var href = alternates[0].getPropertyAsAString("href");
var base;
if (bagHasKey(alternates[0], "xml:base"))
base = strToURI(alternates[0].getPropertyAsAString("xml:base"),
this.baseURI);
else
base = this.baseURI;
this.link = strToURI(alternates[0].getPropertyAsAString("href"), base);
}
catch(e) {
LOG(e);
}
} }
}, },
@ -343,6 +407,7 @@ function Entry() {
this.fields = Cc["@mozilla.org/hash-property-bag;1"]. this.fields = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag2); createInstance(Ci.nsIWritablePropertyBag2);
this.link = null; this.link = null;
this.baseURI = null;
} }
Entry.prototype = { Entry.prototype = {
@ -386,8 +451,11 @@ Entry.prototype = {
_content: ["content:encoded","atom03:content","atom:content"] _content: ["content:encoded","atom03:content","atom:content"]
}, },
normalize: function Feed_normalize() { normalize: function Entry_normalize() {
fieldsToObj(this, this.searchLists); fieldsToObj(this, this.searchLists);
// Assign Atom link if needed
if (bagHasKey(this.fields, "links"))
this._atomLinksToURI();
}, },
QueryInterface: function(iid) { QueryInterface: function(iid) {
@ -400,6 +468,8 @@ Entry.prototype = {
} }
} }
Entry.prototype._atomLinksToURI = Feed.prototype._atomLinksToURI;
// TextConstruct represents and element that could contain (X)HTML // TextConstruct represents and element that could contain (X)HTML
function TextConstruct() { function TextConstruct() {
this.lang = null; this.lang = null;
@ -752,6 +822,7 @@ function FeedProcessor() {
this._buf = ""; this._buf = "";
this._feed = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2); this._feed = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
this._handlerStack = []; this._handlerStack = [];
this._xmlBaseStack = [];
this._depth = 0; this._depth = 0;
this._state = "START"; this._state = "START";
this._result = null; this._result = null;
@ -877,6 +948,7 @@ FeedProcessor.prototype = {
if (uri) { if (uri) {
this._result.uri = uri; this._result.uri = uri;
this._reader.baseURI = uri; this._reader.baseURI = uri;
this._xmlBaseStack[0] = uri;
} }
}, },
@ -885,6 +957,8 @@ FeedProcessor.prototype = {
// than the root. // than the root.
_docVerified: function FP_docVerified(version) { _docVerified: function FP_docVerified(version) {
this._result.doc = Cc[FEED_CONTRACTID].createInstance(Ci.nsIFeed); this._result.doc = Cc[FEED_CONTRACTID].createInstance(Ci.nsIFeed);
this._result.doc.baseURI =
this._xmlBaseStack[this._xmlBaseStack.length - 1];
this._result.doc.fields = this._feed; this._result.doc.fields = this._feed;
this._result.version = version; this._result.version = version;
}, },
@ -991,6 +1065,13 @@ FeedProcessor.prototype = {
++this._depth; ++this._depth;
var elementInfo; var elementInfo;
// Check for xml:base
var base = attributes.getValueFromName(XMLNS, "base");
if (base) {
this._xmlBaseStack[this._depth] =
strToURI(base, this._xmlBaseStack[this._xmlBaseStack.length - 1]);
}
// To identify the element we're dealing with, we look up the // To identify the element we're dealing with, we look up the
// namespace URI in our gNamespaces dictionary, which will give us // namespace URI in our gNamespaces dictionary, which will give us
// a "canonical" prefix for a namespace URI. For example, this // a "canonical" prefix for a namespace URI. For example, this
@ -1071,6 +1152,10 @@ FeedProcessor.prototype = {
if (elementInfo && !elementInfo.isWrapper) if (elementInfo && !elementInfo.isWrapper)
this._closeComplexElement(elementInfo); this._closeComplexElement(elementInfo);
// cut down xml:base context
if (this._xmlBaseStack.length == this._depth + 1)
this._xmlBaseStack = this._xmlBaseStack.slice(0, this._depth);
// our new state is whatever is at the top of the stack now // our new state is whatever is at the top of the stack now
if (this._stack.length > 0) if (this._stack.length > 0)
this._state = this._stack[this._stack.length - 1][1]; this._state = this._stack[this._stack.length - 1][1];
@ -1115,6 +1200,7 @@ FeedProcessor.prototype = {
obj = elementInfo.containerClass.createInstance(Ci.nsIFeedEntry); obj = elementInfo.containerClass.createInstance(Ci.nsIFeedEntry);
// Set the parent property of the entry. // Set the parent property of the entry.
obj.parent = this._result.doc; obj.parent = this._result.doc;
obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
props = obj.fields; props = obj.fields;
} }
else { else {

Просмотреть файл

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with random link relations
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.snellspace.com/public/linktests/alternate";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:snellspace.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-01-18T15:10:00Z</updated>
<author><name>James Snell</name></author>
<link href="http://www.intertwingly.net/wiki/pie/LinkConformanceTests" />
<link rel="self" href="http://www.snellspace.com/public/linktests.xml" />
<entry>
<id>tag:snellspace.com,2006:/atom/conformance/linktest/2</id>
<title>Two alternate links</title>
<updated>2005-01-18T15:00:02Z</updated>
<summary>The aggregator should pick either the second or third link below as the alternate</summary>
<link rel="ALTERNATE" href="http://www.snellspace.com/public/linktests/wrong" />
<link href="http://www.snellspace.com/public/linktests/alternate" />
<link type="text/plain" href="http://www.snellspace.com/public/linktests/alternate2" />
<link rel="ALTERNATE" href="http://www.snellspace.com/public/linktests/wrong" />
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with random link relations
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.snellspace.com/public/linktests/alternate";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:snellspace.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-01-18T15:10:00Z</updated>
<author><name>James Snell</name></author>
<link href="http://www.intertwingly.net/wiki/pie/LinkConformanceTests" />
<link rel="self" href="http://www.snellspace.com/public/linktests.xml" />
<entry>
<id>tag:snellspace.com,2006:/atom/conformance/linktest/3</id>
<title>One of each core link rel type</title>
<updated>2005-01-18T15:00:03Z</updated>
<summary>The aggregator should pick the first link as the alternate</summary>
<link href="http://www.snellspace.com/public/linktests/alternate" />
<link rel="enclosure" href="http://www.snellspace.com/public/linktests/enclosure" length="19" />
<link rel="related" href="http://www.snellspace.com/public/linktests/related" />
<link rel="self" href="http://www.snellspace.com/public/linktests/self" />
<link rel="via" href="http://www.snellspace.com/public/linktests/via" />
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with random link relations
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.snellspace.com/public/linktests/alternate";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:snellspace.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-01-18T15:10:00Z</updated>
<author><name>James Snell</name></author>
<link href="http://www.intertwingly.net/wiki/pie/LinkConformanceTests" />
<link rel="self" href="http://www.snellspace.com/public/linktests.xml" />
<entry>
<id>tag:snellspace.com,2006:/atom/conformance/linktest/4</id>
<title>One of each core link rel type + An additional alternate link</title>
<updated>2005-01-18T15:00:04Z</updated>
<summary>The aggregator should pick either the first or last links as the alternate. First link is likely better.</summary>
<link href="http://www.snellspace.com/public/linktests/alternate" />
<link rel="enclosure" href="http://www.snellspace.com/public/linktests/enclosure" length="19" />
<link rel="related" href="http://www.snellspace.com/public/linktests/related" />
<link rel="self" href="http://www.snellspace.com/public/linktests/self" />
<link rel="via" href="http://www.snellspace.com/public/linktests/via" />
<link rel="alternate" type="text/plain" href="http://www.snellspace.com/public/linktests/alternate2" />
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with IANA URI link relations
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.snellspace.com/public/alternate";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:snellspace.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-01-18T15:10:00Z</updated>
<author><name>James Snell</name></author>
<link href="http://www.intertwingly.net/wiki/pie/LinkConformanceTests" />
<link rel="self" href="http://www.snellspace.com/public/linktests.xml" />
<entry>
<id>tag:example.org,2006:/linkreltest/1</id>
<title>Does your reader support http://www.iana.org/assignments/relation/alternate properly? </title>
<updated>2006-04-25T12:12:12Z</updated>
<link rel="http://example.org/random"
href="http://www.snellspace.com/public/random" />
<link rel="http://www.iana.org/assignments/relation/alternate"
href="http://www.snellspace.com/public/alternate" />
<link rel="http://example.org/random"
href="http://www.snellspace.com/public/random" />
<content>This entry uses link/@rel="http://www.iana.org/assignments/relation/alternate".</content>
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with random link relations
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.snellspace.com/public/linktests/alternate";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:snellspace.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-01-18T15:10:00Z</updated>
<author><name>James Snell</name></author>
<link href="http://www.intertwingly.net/wiki/pie/LinkConformanceTests" />
<link rel="self" href="http://www.snellspace.com/public/linktests.xml" />
<entry>
<id>tag:snellspace.com,2006:/atom/conformance/linktest/5</id>
<title>Entry with a link relation registered by an extension</title>
<updated>2005-01-18T15:00:05Z</updated>
<summary>The aggregator should ignore the license link without throwing any errors. The first link should be picked as the alternate.</summary>
<link href="http://www.snellspace.com/public/linktests/alternate" />
<link rel="payment" href="http://www.example.org/payment" />
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with random link relations
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.snellspace.com/public/linktests/alternate";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:snellspace.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-01-18T15:10:00Z</updated>
<author><name>James Snell</name></author>
<link href="http://www.intertwingly.net/wiki/pie/LinkConformanceTests" />
<link rel="self" href="http://www.snellspace.com/public/linktests.xml" />
<entry>
<id>tag:snellspace.com,2006:/atom/conformance/linktest/6</id>
<title>Entry with a link relation identified by URI</title>
<updated>2005-01-18T15:00:06Z</updated>
<summary>The aggregator should ignore the second link without throwing any errors. The first link should be picked as the alternate.</summary>
<link href="http://www.snellspace.com/public/linktests/alternate" />
<link rel="http://example.org" href="http://www.snellspace.com/public/linktests/example" />
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with random link relations
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.snellspace.com/public/linktests/alternate";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:snellspace.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-01-18T15:10:00Z</updated>
<author><name>James Snell</name></author>
<link href="http://www.intertwingly.net/wiki/pie/LinkConformanceTests" />
<link rel="self" href="http://www.snellspace.com/public/linktests.xml" />
<entry>
<id>tag:snellspace.com,2006:/atom/conformance/linktest/5</id>
<title>Entry with a link relation registered by an extension</title>
<updated>2005-01-18T15:00:05Z</updated>
<summary>The aggregator should ignore the license link without throwing any errors. The first link should be picked as the alternate.</summary>
<link href="http://www.snellspace.com/public/linktests/alternate" />
<link rel="payment" href="http://www.example.org/payment" />
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with random link relations
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.snellspace.com/public/linktests/alternate";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:snellspace.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-01-18T15:10:00Z</updated>
<author><name>James Snell</name></author>
<link href="http://www.intertwingly.net/wiki/pie/LinkConformanceTests" />
<link rel="self" href="http://www.snellspace.com/public/linktests.xml" />
<entry>
<id>tag:snellspace.com,2006:/atom/conformance/linktest/1</id>
<title>Just a single Alternate Link</title>
<updated>2005-01-18T15:00:01Z</updated>
<summary>The aggregator should pick the second link as the alternate</summary>
<link rel="http://example.org/random"
href="http://www.snellspace.com/public/wrong" />
<link href="http://www.snellspace.com/public/linktests/alternate" />
<link rel="http://example.org/random"
href="http://www.snellspace.com/public/wrong" />
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with xml:base
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.example.org/foo";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:example.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-06-18T6:23:00Z</updated>
<link href="http://www.example.org" />
<entry xml:base="http://www.example.org">
<id>tag:example.org,2006:/linkreltest/1</id>
<title>Does your reader support xml:base properly? </title>
<updated>2006-06-23T12:12:12Z</updated>
<link href="foo"/>
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom entry with xml:base
Expect: feed.items.queryElementAt(0, Components.interfaces.nsIFeedEntry).link.spec == "http://www.example.org/bar/foo";
-->
<feed xmlns="http://www.w3.org/2005/Atom">
<id>tag:example.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-06-18T16:13:00Z</updated>
<link href="http://www.example.org" />
<entry xml:base="http://www.example.org">
<id>tag:example.org,2006:/linkreltest/1</id>
<title>Does your reader support xml:base properly? </title>
<updated>2006-06-23T12:12:12Z</updated>
<link xml:base="/bar/" href="foo"/>
</entry>
</feed>

Просмотреть файл

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!--
Description: atom feed with xml:base
Expect: feed.link.spec == "http://www.example.com/foo/bar/baz";
-->
<feed xmlns="http://www.w3.org/2005/Atom"
xml:base="http://www.example.com/foo/bar/">
<id>tag:example.com,2006:/atom/conformance/linktest/</id>
<title>Atom Link Tests</title>
<updated>2005-06-18T16:13:00Z</updated>
<link href="baz" />
<entry xml:base="http://www.example.org">
<id>tag:example.org,2006:/linkreltest/1</id>
<title>Does your reader support xml:base properly? </title>
<updated>2006-06-23T12:12:12Z</updated>
<link xml:base="/bar/" href="foo"/>
</entry>
</feed>