As hypertext use on the Web becomes more sophisticated, identifying document fragments using only IDs included in the document has become more and restrictive. To expand linking possibilities, this document specifies a css() scheme for use in fragment identifiers of HTML and XML content. The css() scheme uses CSS Selectors syntax to identify locations within a given HTML or XML representation of a resource. It is designed to be compatible with XPointer [[!XPTR-FRAMEWORK]], but may also be used in contexts (such as HTML), where XPointer is not formally supported.

Introduction

Developers creating links on the Web have long faced a challenge: it's extremely easy to point a link to any document on the web, but relatively difficult to point to a specific location within a document. HTML fragment identifiers, as defined in the registration for the text/html media type [[RFC2854]] operate on id attributes and (now less frequently) the name attribute of the a, applet, frame, iframe, img and map elements. Making effective use of this mechanism requires either control of the targeted document or generous creators of targeted documents who have liberally applied id attributes throughout a document.

Other mechanisms could easily supplement the use of id attributes. CSS selectors are widely implemented in browsers and other DOM-based environments. While they were not created with linking in mind, they are certainly capable of identifying content in both HTML and XML documents for flexible linking.

The XML community addressed similar concerns with the creation of the XPointer Framework [[!XPTR-FRAMEWORK]] and the XPointer Scheme Name Registry Policy [[!XPTR-POLICY]]. The former provided a syntax and processing framework supporting identification of document fragments, while the latter acknowledged that there are many ways to identify those fragments.

The CSS-based css() scheme may be used within the XPointer Framework [[!XPTR-FRAMEWORK]] to support the addressing of nodes within HTML or XML documents. Like the xpointer() scheme [[XPTR-XPOINTER-CR2001]], the css() scheme supports addressing into the internal structures of documents, addressing documents' hierarchical structure and internal parts based on various properties, including element types, attribute values, character content, and relative position.

Syntax

The scheme name is "css". The scheme data syntax is as follows; if scheme data in a pointer part with the css() scheme does not conform to the syntax defined in this section, it is an error and the pointer part fails (and is ignored).

css() Scheme Syntax:

	  ptrpart           ::=    css( cssschemedata )
	  cssschemedata     ::=    Expr

Expr is as defined as including syntax defined in the Selectors Level 3 [[!SELECT]] specification. All simple selectors, all combinators, and all pseudo-classes may be used with the exceptions of the user action pseudo-classes (http://www.w3.org/TR/css3-selectors/#the-user-action-pseudo-classes-hover-act) and :target. Pseudo-elements may be used in these identifiers, though for the most part they add complexity with little benefit. ::after may be a useful exception.

Should :focus be included?

:visited may create a privacy breach that needs further consideration.

Should pseudo-elements be allowed at all?

Example

Someone wants to link to the second paragraph of an article. The article markup looks something like:

...
<div class="content">
   <p id="first">Neque porro quisquam est qui</p>
   <p id="second">dolorem ipsum quia dolor sit amet</p>
   <p id="third">consectetur, adipisci velit.</p>
</div>
....

With that structure, it's easy to link to the second paragraph with something like:

http://example.com/lorem.html#second

Normal reality, though, tends to look more like:

...
<div class="content">
   <p>Neque porro quisquam est qui</p>
   <p>dolorem ipsum quia dolor sit amet</p>
   <p>consectetur, adipisci velit.</p>
</div>
.... 

Only the people in control of the document can add IDs. Scattering IDs all over the place is not always a best practice either, especially once the document is "complete".

So how could you get to the second paragraph? With something like:

http://example.com/lorem.html#css(.content:nth-child(2))

Or, if you're working in (XML) environments where class selectors might not behave:

http://example.com/lorem.html#css(div[class~="content"]:nth-child(2))

You could get far more complicated, of course, and id attributes are still useful anchor points, but this lets you get reasonably close to a given point in a typical HTML document. It is also unlikely to interfere with classic ID-based fragment identifiers, unless an HTML5 document has used its recently-specified freedom from prior strictures on id attributes to create id attributes with values that look like css(...).

Processing

When applying a pointer to a document type for which the ptrpart syntax can be a valid ID, a processor MUST first attempt to match an element with such an ID, and if there is it MUST return it in place of anything identified by the selector.

A processor MUST support the XPointer xmlns() Scheme [[!XPTR-XMLNS]] and use it to provide a namespace context to the selectors. When evaluation a css() pointer part in the context of a given document, a processor MUST return either a location-set or an error when either the cssschemedata expression is invalid or there is no match in the document. If an error is returned for a css() pointer part, the processor MUST ignore it and move to processing the next pointer part in the URI, if any. If a location-set is returned, then it is the result of evaluating the XPointer and the processor MUST ignore following pointer parts.

If a location-set contains multiple nodes, the processor SHOULD consider that this fragment identifier is pointing to all of them simultaneously (e.g. for :target processing for instance). However, the identification of multiple results may not be appropriate in some contexts. In these contexts, the processor MUST return only the first result in document order.

CSS Versions

Selectors Level 3 [[!SELECT]], is nearing specification completion and already has seen substantial adoption, following past development of Levels 1 and 2. There may eventually be Levels 4 and beyond, which will require more sophisticated processors. Processors MUST ignore selectors that do not match the grammar of the CSS Selectors level that they understand. If a css() pointer part ends up being empty due to selector expressions being ignored, then the processor MUST return an error for it.

Fallbacks

Section 3.3 of [[!XPTR-FRAMEWORK]], Scheme-Based Pointer, specifies that:

When multiple pointer parts are provided, an XPointer processor must evaluate them in left-to-right order. If the XPointer processor does not support the scheme used in a pointer part, it skips that pointer part. If a pointer part does not identify any subresources, evaluation continues and the next pointer part, if any, is evaluated. The result of the first pointer part whose evaluation identifies one or more subresources is reported by the XPointer processor as the result of the pointer as a whole, and evaluation stops. If no pointer part identifies subresources, it is an error.

This left-to-right evaluation allows users of XPointer to try multiple approaches to reach a target. If the first one fails, the second may work. This allows the mixing of schemes to create more robust results in different environments, and, in the case of css(), simplifies falling back to earlier CSS syntax when risking the use of more recent versions. The example used earlier, for instance, relied on CSS3 selector syntax:

http://example.com/lorem.html#css(.content:nth-child(2))

If there was a chance that it would be used in a processor that only supported CSS2 selectors, it might be better rewritten as:

http://example.com/lorem.html#css(.content:nth-child(2))css(#second)css(.content)

The first use is the same as before, the second call to css() hopes for the presence of an ID, and if that fails, the third call gets sort of close enough by just going to the element with the content class.

Multiple Components in the Hash Space

The fragment identifier space of URIs has become more complex since it was first specified, as noted in Repurposing the Hash Sign for the New Web [[HASH-IN-URI]]. This may lead to other complications with stacked information following the hash sign.

Relation to MIME Media Types

URI Generic Syntax [[!URI]] Section 3.5 specifies that:

Individual media types may define their own restrictions on or structures within the fragment identifier syntax for specifying different types of subsets, views, or external references that are identifiable as secondary resources by that media type.

Thus, MIME Media type registrations made in accordance with RFC 2046 [[RFC2046]] and RFC 2048 [[MIME-REG]] may indicate that the css() scheme is applicable to their contents. Those creating XML-based registrations made in accordance with RFC 3023 [[XML-MT]] or its successors should consider including the css() scheme through a broader discussion of XPointer.

In advance of that registration, however, it may make sense for browsers and other processors to experiment with support for the css() scheme. CSS has been used with HTML for over a decade, in particular.

Security Considerations

While it is conceivable that faulty CSS selector could be used to overflow or overload CSS processors used to handle the css() scheme data, the author is not aware of any such assaults having taken place. CSS processors should be robust enough to handle or reject selectors of all kinds in order to process this scheme.

As noted above, the :visited pseudo-class may create privacy issues.

Known Implementations

Extensions supporting this specification are available for Chrome, Safari, Opera, and Firefox.

Acknowledgements

Thanks to Liam Quin and Michael Smith for their initial encouragement of this project. Many thanks to Bruce Lawson, Daniel Davis, David Storey, and Robin Berjon for their early comments, and to Robin for making our lives so much easier with ReSpec.