Annotea(アノテア)は、W3Cが2000年11月に試作版を公開し、2001年5月の国際会議WWW10で論文を発表したウェブ注釈の仕組みです。ウェブページそのものには手を触れず、コメントや質問を別のサーバーに保存し、同じページを開いた人がその注釈を重ねて読めるようにします。注釈はRDFで記述し、注釈を付けた位置はXPointerで指し、サーバーとはHTTPでやり取りします。
ここで扱う「アノテーション」は、文書に付ける注釈のことです。機械学習の学習データにラベルを付ける作業とは別物なので、そちらを探している場合はデータラベリングのラベル設計と一致率の考え方を参照してください。
まとめ:Annoteaの要点と現在の扱い
- AnnoteaはW3CがSemantic Web Advanced Development(SWAD)の一環として進めた注釈・共有ブックマークの仕組み。2000年11月にAmaya 4.0組み込みのクライアントと公開注釈サーバーを公開し、2001年5月のWWW10(香港)で論文を発表した。
- 注釈はRDFのメタデータで、
annotates(対象ページ)・context(位置)・body(本文)が中心。注釈タイプはComment・Questionなど7種類。 - サーバーとの通信はPOST(作成)・GET(検索)・PUT(更新)・DELETE(削除)の4操作。検索は
w3c_annotatesパラメータで行う。 - 位置指定をXPointer 1本に頼ったため、元文書を編集すると注釈が行き場を失う「孤立注釈」が起きた。
- W3Cの公開試験サーバー(annotest.w3.org)は2026年10月時点でDNSから消えており、最初のクライアントだったAmayaも2012年1月の11.4.4で開発を終えている。
- 後継は2017年2月23日に勧告されたW3C Web Annotation(データモデル・語彙・プロトコルの3本)。新しく注釈機能を作るならこちらを使う。
以下、仕様書の記述をもとに、データの形・プロトコル・失敗の原因・後継仕様への移し替えの順に見ていきます。
Annoteaの成り立ち:W3C SWADの実証プロジェクト
AnnoteaのトップページはAnnoteaを「SWADのもとのW3C LEAD(Live Early Adoption and Demonstration)プロジェクト」と位置づけています。標準化の文書を作る作業部会ではなく、W3Cの技術を組み合わせて動かしてみせる実証の取り組みでした。そのためAnnotea Protocolも勧告ではなく、ステータス欄に「public DRAFT for discussion」と書かれた討議用の草案のまま終わっています。
WWW10論文「Annotea: An Open RDF Infrastructure for Shared Web Annotations」の著者はJosé Kahan(W3C/INRIA Rhône-Alpes)、Marja-Riitta Koivunen、Eric Prud’hommeaux、Ralph R. Swick(いずれもW3C/MIT LCS)の4名です。論文によると、試作のクライアントにはW3Cのエディタ兼ブラウザAmaya(4.0)を使い、サーバーはApache上でMySQLとPerlスクリプトを動かす構成でした。注釈と文書の重ね合わせはクライアント側で行います。
| 時期 | 出来事 |
|---|---|
| 2000年11月 | Amaya 4.0に組み込んだクライアントと公開注釈サーバーを公開 |
| 2001年3月9日 | Annoteaの告知とホームページ公開 |
| 2001年5月 | WWW10でAnnotea論文を発表 |
| 2001年12月17日 | Amayaが注釈への返信(スレッド表示)に対応 |
| 2002年7月24日 | W3CのJavaScriptブックマークレット用サーブレットを停止 |
| 2002年12月19日 | Annotea Protocolの改訂版を公開 |
| 2003年7月 | ブックマークスキーマ公開・Amaya 8.1(Linux版)が共有ブックマークに部分対応 |
| 2005年10月31日 | Annoteaトップページの最終更新 |
| 2012年1月18日 | Amaya最終版11.4.4 |
| 2017年2月23日 | 後継のWeb Annotation 3仕様がW3C勧告に |
利用規模は大きくありませんでした。WWW10の開発者向けセッションのスライドには、2001年4月1日から30日までの公開サーバーannotest.w3.orgの統計(W3Cスタッフを除く)として、利用者123人、新規注釈93件(22人)、annotatesの問い合わせ1,920回が載っています。W3C外の実装もあり、W3Cの関連プロジェクト一覧(最終更新2006年12月25日)にはAnnozillaなどのクライアント9件、Zope上のZannotやPythonのPyNoteaなどのサーバー5件、JavaライブラリWAINUが並んでいます。一方、ブックマークレットのサーブレットは、トップページのニュース欄に「維持する資源が足りないため」停止したと書かれています。
注釈データのRDFスキーマ:プロパティと7つの注釈タイプ
annotation-nsで定義されたプロパティ
注釈の語彙は名前空間http://www.w3.org/2000/10/annotation-ns#で定義されています。名前空間文書(Revision 1.5、2003年7月31日)にあるプロパティは次のとおりです。
| プロパティ | 意味 |
|---|---|
annotates |
注釈を付けた対象のリソース |
context |
対象内の位置(XPointer) |
body |
注釈の本文となるリソース |
author |
注釈の作成者 |
created |
作成日時 |
modified |
更新日時 |
related |
bodyより緩い関連リソース |
注意点が1つあります。スキーマにはauthorがありますが、Annotea Protocolの例は作成者をDublin Coreのdc:creatorで書いています。更新日時も、名前空間文書自身が「現在はdc:dateを直接使っている」と注記しています。古い注釈データを読むときは、両方の書き方が混在している前提で処理する必要があります。
annotationTypeの7クラス
注釈の種類は別の名前空間http://www.w3.org/2000/10/annotationType#で、Annotationのサブクラスとして7つ定義されています。
| クラス | 用途 |
|---|---|
| Comment | 一般的なコメント |
| Question | 内容への質問 |
| Explanation | 内容の説明 |
| Example | 例示 |
| SeeAlso | 別リソースへの参照 |
| Change | 変更の通知・提案 |
| Advice | 読者への助言 |
WWW10論文はこの名前空間をannotationTypes#と複数形で書いていますが、実在する名前空間文書とプロトコルの例は単数形のannotationType#です。RDFでは1文字違えば別のURIになるので、論文から語彙を写すと型の判定が一致しなくなります。
Annotea Protocolの4操作:POST・GET・PUT・DELETE
2002年12月19日版のAnnotea Protocolは、作成にPOST、検索と取得にGET、更新にPUT、削除にDELETEを使うと定めています。作成の要求はRDF/XMLを本文に入れて送ります。仕様書の例は次のとおりです。
POST /Annotation HTTP/1.1
Host: annotea.example.org
Content-Type: application/xml
<?xml version="1.0" ?>
<r:RDF xmlns:r="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:a="http://www.w3.org/2000/10/annotation-ns#"
xmlns:d="http://purl.org/dc/elements/1.1/">
<r:Description>
<r:type r:resource="http://www.w3.org/2000/10/annotation-ns#Annotation"/>
<r:type r:resource="http://www.w3.org/2000/10/annotationType#Comment"/>
<a:annotates r:resource="http://serv1.example.com/some/page.html"/>
<a:context>
http://serv1.example.com/some/page.html#xpointer(id("Main")/p[2])
</a:context>
<d:title>Annotation of Sample Page</d:title>
<d:creator>Ralph Swick</d:creator>
<a:created>1999-10-14T12:10Z</a:created>
<d:date>1999-10-14T12:10Z</d:date>
<a:body r:resource="http://serv2.example.com/mycomment.html"/>
</r:Description>
</r:RDF>
サーバーは201 Createdを返し、Locationヘッダーで新しい注釈のURIを知らせます。応答の本文には、注釈のURI・annotates・bodyを含むRDFが入ります。本文を別URLに置かず要求の中に埋め込む書き方もあり、その場合はHTTPヘッダーをRDFのプロパティとして表す独自の名前空間http://www.w3.org/1999/xx/http#を使います。
| 操作 | メソッド | 要求の形 |
|---|---|---|
| 作成 | POST | RDF/XMLを送信→201+Location |
| ページの注釈を検索 | GET | ?w3c_annotates=対象URL |
| 返信スレッドを取得 | GET | ?w3c_reply_tree=注釈URI |
| 更新 | PUT | 注釈URIへ送信 |
| 更新(W3C公開サーバー) | POST | ?replace_source=注釈URI |
| 削除 | DELETE | 注釈URIへ送信 |
仕様書はw3c_annotatesを、プロパティannotation-ns#annotatesの省略形と説明しています。つまり「このページをannotatesに持つ注釈を返せ」という問い合わせです。より複雑な検索は付録AのAlgaeというRDF問い合わせ言語で書きます。SPARQLのW3C勧告は2008年1月なので、Annoteaの時代にはまだありません。注釈をSPARQLで検索する、という説明を見かけたら後年の実装の話と考えてください(SPARQLそのものはSPARQLの書き方とSQLとの違いで解説しています)。
もう1つ、仕様書全体を検索してもETagとIf-Matchは1回も出てきません。2人が同じ注釈を同時に書き換えたときに後から来た更新が先の更新を黙って上書きしても、それを防ぐ取り決めがないということです。後述のWeb Annotation Protocolは、GETとHEADの応答にETagを必須(MUST)とし、更新時にそのETagをIf-Matchで送ることを推奨(SHOULD)して、この穴を埋めています。
XPointerによる位置指定と孤立注釈
注釈を付けた箇所はcontextにXPointerで書きます。上の例のid("Main")/p[2]は「idがMainの要素の2番目のp要素」を指します。WWW10論文はこの方式について、変化しない文書ならうまく働くが、作業部会の草案のように改訂される文書では「孤立注釈(orphan annotation)や間違った場所を指す注釈」が生じうると書いています。
論文が挙げる対策は、対象に最も近い祖先のidを起点にXPointerを組むことです。文書の主要な階層にはたいていidがあるので、idより下の経路が短くなり、壊れにくくなります。それでも壊れた場合、Amayaは利用者に警告し、孤立した注釈をリンク一覧(Links view)にまとめて表示しました。
この設計の弱点は、位置の手がかりが1種類しかないことです。段落を1つ足すだけでp[2]は別の段落を指し、しかも注釈側にはどの文字列に付けたのかという記録がありません。間違った場所に付いたことをソフトウェアが検出する手段がないのです。Annoteaが広がらなかった理由は、主要ブラウザが対応しなかったことなど複数あるはずですが、仕様書から確実に言えるのはこの1点です。後継仕様と現在の実装は、まさにここを作り直しています。
後継のW3C Web Annotation:引き継がれた点と変わった点
Open Annotationを経て2017年に勧告
Annoteaのあと、注釈の標準化はW3CのOpen Annotation Community Groupに移りました。同グループは、それぞれ別に作られていたAnnotation OntologyとOpen Annotation Modelを1つの仕様にまとめることを目的としていました(グループ自体は2023年4月7日に閉鎖)。その成果がWeb Annotation Working Groupに引き継がれ、2017年2月23日にWeb Annotation Data Model、Web Annotation Vocabulary、Web Annotation Protocolの3本がW3C勧告になりました。Annoteaの草案と違い、こちらは正式な標準です。
Annoteaとの対応表
| 項目 | Annotea(2002年草案) | Web Annotation(2017年勧告) |
|---|---|---|
| 文書の位置づけ | 討議用草案 | W3C勧告 |
| 形式 | RDF/XML | JSON-LD(推奨) |
| 対象 | annotates |
target |
| 本文 | body(URL) |
body(URLまたはTextualBody) |
| 位置 | XPointer 1本 | セレクタ8種を複数併記可 |
| 種類 | サブクラス7種 | motivation 13種 |
| サーバー | 独自のクエリパラメータ | LDPコンテナ |
| 同時更新 | 規定なし | 応答にETag必須・If-Match推奨 |
| サーバーの発見 | 利用者が設定 | Linkヘッダー(oa#annotationService) |
Web Annotation Data Modelが定める8種のセレクタは、Fragment・CSS・XPath・TextQuote・TextPosition・DataPosition・SVG・Rangeです。さらにrefinedByで、あるセレクタで絞った結果を別のセレクタでもう一段絞れます。データモデルは1つの対象に複数のセレクタを付けることを認めており、「複数のセレクタは同じ内容を選択すべき(SHOULD)」で、異なる場合は利用側が1つを選ばなければならない(MUST)と定めています。Annoteaの「XPointer 1本」に対する直接の答えがこの規定です。
形式の面では、メディアタイプがapplication/ld+json;profile="http://www.w3.org/ns/anno.jsonld"と定められ、RDFの道具を使わなくてもJSONとして読み書きできるようになりました。RDFとしての意味づけは保たれているので、RDFとOWLで意味を定義するオントロジーの仕組みにもそのまま載ります。
注釈タイプからmotivationへの読み替え
Annoteaの7クラスとWeb Annotationの13のmotivation(assessing・bookmarking・classifying・commenting・describing・editing・highlighting・identifying・linking・moderating・questioning・replying・tagging)は1対1には対応しません。意味が素直に重なるのは次の4つです。
| Annoteaのクラス | 近いmotivation |
|---|---|
| Comment | commenting |
| Question | questioning |
| SeeAlso | linking |
| Change | editing |
| Explanation・Example・Advice | 対応なし(commenting+タグで保持) |
Explanationをdescribingに寄せる手もありますが、describingは「対象についてのコメントではなく対象そのものの記述」と定義されており、Annoteaの「内容の説明」とは意図がずれます。無理に当てはめず、元のクラス名をtagging目的の本文として残しておくほうが、後で区別し直せます。返信スレッドはreplying、共有ブックマークはbookmarkingに移せます。
古いAnnoteaデータをWeb Annotationへ移すコード
Annoteaの注釈をRDF/XMLで書き出したデータが手元に残っている場合、Annotea Protocolの例と同じ書き方(r:Descriptionにr:typeでAnnotationを付けた形、またはa:Annotation要素)であれば、Pythonの標準ライブラリだけでWeb AnnotationのJSON-LDに変換できます。次のコードは上の表の読み替えを実装したものです。作成者はdc:creatorとauthorの両方を、更新日時はmodifiedとdc:dateの両方を読み、本文を要求に埋め込んだ形(h:Body)はTextualBodyに移します。
import json, re, sys
import xml.etree.ElementTree as ET
R = "{http://www.w3.org/1999/02/22-rdf-syntax-ns#}"
A = "{http://www.w3.org/2000/10/annotation-ns#}"
D = "{http://purl.org/dc/elements/1.1/}"
H = "{http://www.w3.org/1999/xx/http#}"
ANNOTATION = "http://www.w3.org/2000/10/annotation-ns#Annotation"
TYPES = "http://www.w3.org/2000/10/annotationType#"
XML_FRAGMENT = "http://tools.ietf.org/rfc/rfc3023" # xpointer(...) を値に取る
# Annotea の注釈タイプ -> Web Annotation の motivation(対応が素直なものだけ)
MOTIVATION = {"Comment": "commenting", "Question": "questioning",
"SeeAlso": "linking", "Change": "editing"}
ET.register_namespace("", "http://www.w3.org/1999/xhtml")
def dt(value):
if value and re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\dZ", value.strip()):
return value.strip()[:-1] + ":00Z" # 秒を補って xsd:dateTime に揃える
return value.strip() if value else None
def person(name):
return {"type": "Person", "name": name.strip()} if name else None
def is_annotation(el):
if el.tag == A + "Annotation":
return True
return any(t.get(R + "resource") == ANNOTATION for t in el.findall(R + "type"))
def body_of(el):
body = el.find(A + "body")
if body is None:
return None
if body.get(R + "resource"):
return body.get(R + "resource")
# 本文を要求に埋め込んだ形(h:Body)は TextualBody にする
desc = body.find(R + "Description")
h = desc.find(H + "Body") if desc is not None else None
if h is None:
return None
value = (h.text or "").strip() + "".join(ET.tostring(c, encoding="unicode") for c in h)
return {"type": "TextualBody", "value": value.strip(),
"format": (desc.findtext(H + "ContentType") or "text/plain").strip()}
def convert(el):
types = [t.get(R + "resource", "") for t in el.findall(R + "type")]
kinds = [t[len(TYPES):] for t in types if t.startswith(TYPES)]
target = {"source": el.find(A + "annotates").get(R + "resource")}
ctx = (el.findtext(A + "context") or "").strip()
if "#" in ctx:
fragment = ctx.split("#", 1)[1]
selectors = [{"type": "FragmentSelector", "conformsTo": XML_FRAGMENT, "value": fragment}]
m = re.fullmatch(r'xpointer\(id\("([^"]+)"\)(.*)\)', fragment)
if m: # id("X")/... の形だけ XPath に書き直せる
selectors.insert(0, {"type": "XPathSelector",
"value": '//*[@id="%s"]%s' % (m.group(1), m.group(2))})
target["selector"] = selectors if len(selectors) > 1 else selectors[0]
anno = {
"@context": "http://www.w3.org/ns/anno.jsonld",
"id": el.get(R + "about"),
"type": "Annotation",
"motivation": MOTIVATION.get(kinds[0], "commenting") if kinds else "commenting",
"creator": person(el.findtext(D + "creator") or el.findtext(A + "author")),
"created": dt(el.findtext(A + "created")),
"modified": dt(el.findtext(A + "modified") or el.findtext(D + "date")),
"body": body_of(el),
"target": target,
}
if kinds and kinds[0] not in MOTIVATION:
# Advice / Example / Explanation は対応する motivation が無いので
# 元のタイプ名を tagging 目的の本文として残す
first = anno["body"]
if isinstance(first, str):
first = {"type": "SpecificResource", "source": first, "purpose": "commenting"}
elif first:
first = dict(first, purpose="commenting")
anno["body"] = [b for b in (first, {"type": "TextualBody", "value": kinds[0],
"purpose": "tagging"}) if b]
return {k: v for k, v in anno.items() if v is not None}
root = ET.parse(sys.argv[1]).getroot()
annotations = [convert(el) for el in root if is_annotation(el)]
print(json.dumps(annotations, ensure_ascii=False, indent=2))
HTTPヘッダー部分を除き、<?xmlから始まるRDF/XMLだけをannotea.xmlとして保存し、python3 annotea2wa.py annotea.xml > annotations.jsonで実行します。出力は注釈の配列です。上の作成例(r:aboutに注釈URIを足したもの)を入力すると、次のJSON-LDになります。埋め込み本文・a:Annotation要素・authorだけの入力でも実行し、それぞれ変換されることを確認しています。
[
{
"@context": "http://www.w3.org/ns/anno.jsonld",
"id": "http://annotea.example.org/Annotation/3ACF6D754",
"type": "Annotation",
"motivation": "commenting",
"creator": {
"type": "Person",
"name": "Ralph Swick"
},
"created": "1999-10-14T12:10:00Z",
"modified": "1999-10-14T12:10:00Z",
"body": "http://serv2.example.com/mycomment.html",
"target": {
"source": "http://serv1.example.com/some/page.html",
"selector": [
{
"type": "XPathSelector",
"value": "//*[@id=\"Main\"]/p[2]"
},
{
"type": "FragmentSelector",
"conformsTo": "http://tools.ietf.org/rfc/rfc3023",
"value": "xpointer(id(\"Main\")/p[2])"
}
]
}
}
]
元のXPointerは、XML文書のフラグメント(RFC 3023)としてFragmentSelectorにそのまま残します。id("X")/...の形だけはXPathSelectorにも書き直して先頭に置きますが、それ以外の形は書き直さないので、位置の情報が落ちて文書全体への注釈に変わることはありません。XPathはHTMLとして解析したDOMで評価する前提です。どちらのセレクタも元のXPointerと同じ場所を指すだけなので、孤立しやすさは元のままです。移行で本当に価値が出るのは、元文書の該当箇所から文字列を取り出してTextQuoteSelector(exact・prefix・suffix)を併記するところです。ただし元文書が注釈当時の版のまま残っている場合に限られます。文書が改訂済みなら、XPathが今指している段落は当時の段落ではないかもしれません。その状態で文字列を取ると、誤った位置を「正しい」と確定させてしまいます。改訂履歴を持つ文書は、版を特定できない限りXPathのまま移し、TextQuoteは付けないほうが安全です。
現在の注釈ツールの位置指定:Hypothesisの実装
Web Annotationの考え方で動いている代表例が、オープンソースの注釈ツールHypothesisです。サーバー(hypothesis/h)はBSD 2条項ライセンスで、2026年9月28日にもmainブランチへのコミットがある現役のプロジェクトです。Hypothesisも最初はAnnoteaと同じ弱点を抱えていました。2013年4月22日のブログ記事「Fuzzy Anchoring」によると、当初はAnnotatorプロジェクトから受け継いだ方式で、DOM要素へのXPathと文字オフセットだけを保存していました。これを3種類のセレクタの保存と4段階の再付着に改めています。現在のブラウザ側(hypothesis/client)のsrc/annotator/anchoring/html.tsを読むと、その仕組みが具体的に分かります。
- 注釈を作るとき、選択範囲をRangeSelector・TextPositionSelector・TextQuoteSelectorなど複数の形で同時に記録する。
- 注釈を表示するときは、ソースのコメントどおり「単純なものから複雑なものへ」の順で、Range→TextPosition→TextQuoteと試す。
- RangeとTextPositionで見つけた範囲は、記録しておいた
exactの文字列と一致するかを照合し、一致しなければ「quote mismatch」として次の手段に移る。 - 最後のTextQuoteは
approx-string-matchライブラリによる近似一致で探すので、文字が多少変わっていても見つけられる。
Annoteaとの違いは、位置の手がかりを複数持つことと、見つけた場所が正しいかを文字列で確かめることの2点です。Annoteaでは誤った位置への付着を検出できませんでしたが、この実装では照合に失敗した手段は捨てられます。
Annoteaを今調べる意味と、採用すべきでない場面
Annoteaの知識が役に立つのは、2000年代のRDF/XMLの注釈データや、Amayaで蓄積した注釈を読み解く場面です。名前空間の単数形・複数形の揺れや、dc:creatorとauthorの混在は、知らないと変換で黙ってデータを落とします。社内文書にコメント機能を作る場合も、「元文書を編集すると注釈が迷子になる」というAnnoteaの失敗は、設計の初期に検討すべき論点として今も有効です。
一方で、新しくAnnotea Protocolでサーバーやクライアントを作るのはやめるべきです。理由は3つの事実で足ります。プロトコルは勧告にならなかった草案であること、同時更新の取り決めがないこと、W3Cの公開試験サーバーが無くなり、最初のクライアントAmayaも開発を終えていることです。注釈機能を作るならWeb Annotation Data ModelのJSON-LDで保存し、テキストへの注釈ならTextQuoteSelectorを含む複数のセレクタで位置を記録してください。画像の領域ならSvgSelector、動画の区間ならメディアフラグメントのFragmentSelectorが受け皿です。注釈データを自社サーバーに置く必要がなく、ウェブページやPDFへの注釈で足りる要件なら、自作の前にHypothesisのような既存の実装で満たせるかを確かめてください。
よくある質問
Annoteaとは何ですか?
W3Cが2000年11月に試作版を公開したウェブ注釈の仕組みです。ページを書き換えずに、コメントなどの注釈を別サーバーにRDFで保存し、HTTPで共有します。
Annoteaは今も使えますか?
W3Cの公開試験サーバーannotest.w3.orgは2026年10月時点でドメインが引けず、最初のクライアントAmayaも2012年1月の11.4.4で開発を終えています。使うにはサーバーとクライアントを自分で用意する必要があるので、新しい用途ならWeb Annotationを選ぶほうが現実的です。
AnnoteaとWeb Annotationの違いは何ですか?
Annoteaは討議用の草案で、RDF/XMLとXPointer 1本で位置を指しました。Web Annotationは2017年の勧告で、JSON-LDを推奨し、8種のセレクタを複数併記できます。
Annoteaの注釈はどこに保存されますか?
利用者が選んだ1つ以上の注釈サーバーか、手元のローカルに保存されます。注釈を付けたページ側には何も書き込まれません。
機械学習のアノテーションと同じものですか?
違います。Annoteaは人が文書に付けるコメントの仕組みで、学習データにラベルを付ける作業とは目的もデータ形式も別です。