Skip to content

Commit

Permalink
feat(Microdata): Add higher level HTML Microdata functions
Browse files Browse the repository at this point in the history
  • Loading branch information
nokome committed Feb 23, 2020
1 parent 785430b commit 67b850e
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 8 deletions.
71 changes: 67 additions & 4 deletions ts/util/microdata.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,82 @@
import {
microdataItemtype,
microdataItemprop,
microdataType
microdataType,
microdata,
microdataItem,
microdataProperty
} from './microdata'
import { jsonLdUrl } from './jsonld'
import { codeChunk, article, person, thing, organization } from '../types'

test('microdata', () => {
// A Stencila type
expect(microdata(codeChunk({text: ''}))).toEqual({
itemscope: '',
itemtype: 'http://schema.stenci.la/CodeChunk'
})

// A schema.org type
expect(microdata(article())).toEqual({
itemscope: '',
itemtype: 'http://schema.org/Article'
})

// A schema.org type as a Stencila property
// which is an alias for a schema.org term
expect(microdata(person(), 'authors')).toEqual({
itemscope: '',
itemtype: 'http://schema.org/Person',
itemprop: 'author'
})

// A schema.org type as a Stencila custom property
expect(microdata(2, 'depth')).toEqual({
itemscope: '',
itemtype: 'http://schema.org/Number',
'data-itemprop': 'depth'
})

// Generate microdata for a property who's node
// is encoded as a free-standing element. Use id
// for this.
const org1 = organization()
expect(microdata(org1, 'affiliations', 'org1')).toEqual({
itemscope: '',
itemtype: 'http://schema.org/Organization',
itemprop: 'affiliation',
itemref: 'org1'
})
expect(microdata(org1, undefined, 'org1')).toEqual({
itemscope: '',
itemtype: 'http://schema.org/Organization',
itemid: '#org1'
})
})

test('microdataItem', () => {
expect(microdataItem(organization(), 'org1')).toEqual({
itemscope: '',
itemtype: 'http://schema.org/Organization',
itemid: '#org1'
})
})

test('microdataProperty', () => {
expect(microdataProperty('affiliations', 'org1')).toEqual({
itemprop: 'affiliation',
itemref: 'org1'
})
})

test('microdataItemtype', () => {
expect(microdataItemtype('CodeChunk')).toMatch(jsonLdUrl('CodeChunk'))
expect(microdataItemtype('CodeChunk')).toMatch('http://schema.stenci.la/CodeChunk')
expect(microdataItemtype('Article')).toMatch('http://schema.org/Article')
// @ts-ignore that Foo is not a type
expect(microdataItemtype('Foo')).toBeUndefined()
})

test('microdataType', () => {
expect(microdataType(jsonLdUrl('CodeChunk'))).toMatch('CodeChunk')
expect(microdataType('http://schema.stenci.la/CodeChunk')).toMatch('CodeChunk')
expect('http://schema.org/Article').toMatch('Article')
expect(microdataType('http://example.com')).toBeUndefined()
})
Expand Down
87 changes: 83 additions & 4 deletions ts/util/microdata.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,66 @@
import { Types } from '../types'
import { jsonLdTermUrl, jsonLdContext, jsonLdTermName } from './jsonld'
import { Node, Types } from '../types'
import { jsonLdContext, jsonLdTermName, jsonLdTermUrl, jsonLdUrl } from './jsonld'
import { nodeType } from './node-type'
import { isEntity } from './guards'

/**
* Get the URL used in Microdata attributes.
*
* This is used to normalize the versioned URL from the
* JSON-LD context.
*/
export function microdataUrl(type: string = '') {
return `http://schema.stenci.la/${type}`
}

export type Microdata = MicrodataItem & MicrodataProperty

/**
* Create all Microdata attributes for a Stencila `Node`.
*
* @param node The node e.g. a `Person` node
* @param property The name of the property that this node is part of e.g `'author'`
*/
export function microdata(
node: Node,
property?: string,
id?: string
): Microdata {
return {
...microdataItem(node, property === undefined ? id : undefined),
...(property !== undefined ? microdataProperty(property, id) : {})
}
}

/**
* Attributes for Microdata ["items"](https://www.w3.org/TR/microdata/#items)
*
* "The itemtype attribute must not be specified on elements that do not have
* an itemscope attribute specified."
*/
export interface MicrodataItem {
itemscope: ''
itemtype: string
itemid?: string
}

export function microdataItem(node: Node, id?: string): MicrodataItem {
const itemtype = microdataItemtype(nodeType(node)) ?? 'Thing'
const itemid = id !== undefined ? { itemid: `#${id}` } : {}
return {
itemscope: '',
itemtype,
...itemid
}
}

/**
* Get the HTML Microdata `itemtype` for a Stencila Schema type
*
* @see {@link https://www.w3.org/TR/microdata/#dfn-itemtype}
*/
export function microdataItemtype(type: keyof Types): string | undefined {
return jsonLdTermUrl(type)
return jsonLdTermUrl(type)?.replace(jsonLdUrl(), microdataUrl())
}

/**
Expand All @@ -16,7 +69,33 @@ export function microdataItemtype(type: keyof Types): string | undefined {
* This is the inverse of `microdataItemtype`.
*/
export function microdataType(itemtype: string): keyof Types | undefined {
return jsonLdTermName(itemtype) as keyof Types
return jsonLdTermName(itemtype.replace(microdataUrl(), jsonLdUrl())) as keyof Types
}

/**
* Attributes for Microdata ["properties"](https://www.w3.org/TR/microdata/#names:-the-itemprop-attribute)
*
* The `data-itemprop` attribute is not part of the Microdata standard.
* It is used for properties that are not defined in schema.org and which validators
* like Google Structured Data Testing Tool throw errors about.
*/
export interface MicrodataProperty {
itemprop?: string
'data-itemprop'?: string
itemref?: string
}

/**
* Create `MicrodataProperty` attributes for a node property.
*/
export function microdataProperty(
property: string,
id?: string
): MicrodataProperty {
const [prefix, name = ''] = microdataItemprop(property)
const key = prefix === 'schema' ? 'itemprop' : 'data-itemprop'
const itemref = id !== undefined ? { itemref: id } : {}
return { [key]: name, ...itemref }
}

/**
Expand Down

0 comments on commit 67b850e

Please sign in to comment.