User:Dantman/Output Metadata/Namespace API

From mediawiki.org

This page describes the idea or a MWXMLScope class for the support of an XML/RDFa api. (RDFa prefixes and CURIEs are somewhat different than XML namespaces. MWXMLScope is a terrible name. MWXMLNamespaceScope might be better. But atm we don't even intend to use it for actual XML namespaces but instead for RDFa prefixes so using that name seems a little foolish.)

The goal of this class is to provide a way to use XML/RDFa namespaces using prefixes with a guarantee that you will not end up with conflicts caused by accidentally using the same prefix that something else is already using.

For example our <html> element would have one MWXMLScope tied to it, and we'd query that instance's interfaces to get what curie (ie: og:title, etc...) we would use inside elements in the <head>, etc...

A MWXMLScope instance has the following api:

$scope->namespace( $preferredPrefix, $uri/$iri, [$keepPrefixIfPossible = false] );
Example:
$scope->namespace( 'foaf', "http://xmlns.com/foaf/0.1/" );
Given a preference for the prefix and a uri for the namespace, defines the namespace on the MWXMLScope and returns an MWXMLScope_Namespace instance that can be used to build things using that namespace.
  • The prefix is a preference and it's not guaranteed that the resulting namespace will use the exact same prefix. For example if two extensions ask for a namespace on the same scope using two different namespace URIs and both ask for the prefix 'asdf', only the first uri will get associated with the prefix, the other will be given a different automatically generated prefix.
  • If a previous call has used the same $uri and already registered a prefix this method will typically return that namespace so it doesn't have to wastefully register redundant namespace attributes. If you pass true to $keepPrefixIfPossible this behaviour will be disabled and as long as the prefix preference you provide has not already been used it will return a namespace using your prefix preference even if that means that it has to define two prefixes with the same URI.
$scope->namespaceMap();
Returns an array mapping prefixes to URIs containing the currently registered namespaces.
$scope->xmlnsAttrs();
Returns an array mapping xmlns:prefix keys to the URI which can be used in an attributes array when building an element.
$scope->prefixAttr();
Returns the value for a RDFa Core 1.1 prefix="" attribute

The MWXMLScope_Namespace class returned by MWXMLScope::namespace has the following api:

$ns->prefix();
Return the prefix used in this namespace. Note that it is preferred that you use ->gname( $name ) instead of taking return of ->prefix() and concatenating it with something like "$prefix:$name";
$ns->qname( $name );
Return a qname (eg: "foo:bar") suitable of being used in an XML/XHTML tag name, etc...
$ns->curie( $name );
Return a CURIE (eg: "og:title") suitable of being used in an RDFa property, etc...
$ns->expand( $name );
Return the fully expanded URI for a given name in the namespace.

Examples and expectations defined as a run of test code with assertions:

<?php

$scope = new MWXMLScope; // one tag gets one scope
$ns = $scope->namespace( 'foaf', "http://xmlns.com/foaf/0.1/" );
assertEq( $ns->prefix(), 'foaf' );
assertEq( $ns->curie( 'foo' ), 'foaf:foo' );
assertEq( $ns->expanded( 'foo' ), "http://xmlns.com/foaf/0.1/foo" );

// Calling the same namespace uri and prefix again returns the same namespace
$ns = $scope->namespace( 'foaf', "http://xmlns.com/foaf/0.1/" );
assertEq( $ns->prefix(), 'foaf' );
assertEq( $ns->curie( 'foo' ), 'foaf:foo' );
assertEq( $ns->expanded( 'foo' ), "http://xmlns.com/foaf/0.1/foo" );

// Calling for a namespace with a prefix that already exists asking for a new uri
// results in the system determining a new unique id on it's own
$ns = $scope->namespace( 'foaf', "http://example.org/not-foaf/" );
assertEq( $ns->prefix(), 'foaf_1' );
assertEq( $ns->curie( 'foo' ), 'foaf_1:foo' );
assertEq( $ns->expanded( 'foo' ), "http://example.org/not-foaf/foo" );

// By default calling for a namespace using a previously used url but a new prefix will net you the old prefix to avoid wasteful duplication
$ns = $scope->namespace( 'ffff', "http://xmlns.com/foaf/0.1/" );
assertEq( $ns->prefix(), 'foaf' );
assertEq( $ns->curie( 'foo' ), 'foaf:foo' );
assertEq( $ns->expanded( 'foo' ), "http://xmlns.com/foaf/0.1/foo" );

// However if you make the call while passing true to the third argument it will take this to indicate that you really
// really want the prefix to be what you specify unless it absolutely can't be done (ie: it was already used for a different url)
$ns = $scope->namespace( 'ffff', "http://xmlns.com/foaf/0.1/", true );
assertEq( $ns->prefix(), 'ffff' );
assertEq( $ns->curie( 'foo' ), 'ffff:foo' );
assertEq( $ns->expanded( 'foo' ), "http://xmlns.com/foaf/0.1/foo" );

// The first name for a url is always the preference. Note that here both foaf and ffff refer to the url we pass, none
// match xxxx and we have not specified true, in this case we get foaf instead of ffff because foaf was used first
$ns = $scope->namespace( 'xxxx', "http://xmlns.com/foaf/0.1/" );
assertEq( $ns->prefix(), 'foaf' );
assertEq( $ns->curie( 'foo' ), 'foaf:foo' );
assertEq( $ns->expanded( 'foo' ), "http://xmlns.com/foaf/0.1/foo" );

// The scope stores the whole map of prefixes to urls
assertEq( $scope->namespaceMap(), array(
	'foaf' => "http://xmlns.com/foaf/0.1/",
	'foaf_1' => "http://example.org/not-foaf/",
	'ffff' => "http://xmlns.com/foaf/0.1/",
) );

// The scope is capable of returning the list of xhtmlns attributes
assertEq( $scope->xmlnsAttrs(), array(
	'xmlns:foaf' => "http://xmlns.com/foaf/0.1/",
	'xmlns:foaf_1' => "http://example.org/not-foaf/",
	'xmlns:ffff' => "http://xmlns.com/foaf/0.1/",
) );

// The scope can also return a RDFa Core 1.1 prefix="" attribute
assertEq( $scope->prefixAttr(), "foaf: http://xmlns.com/foaf/0.1/ foaf_1: http://example.org/not-foaf/ ffff: http://xmlns.com/foaf/0.1/" )