To receive notifications about scheduled maintenance, please subscribe to the mailing-list gitlab-operations@sympa.ethz.ch. You can subscribe to the mailing-list at https://sympa.ethz.ch

Commit ca7567a2 authored by vermeul's avatar vermeul
Browse files

Initial commit

parents
.DS_Store
*~
<?php
/**
* YATA extension
* @author Swen Vermeul
* @file
* @version 0.1
* @license GNU General Public Licence 2.0
*/
class YATA {
// Register any render callbacks with the parser
public static function onParserSetup( &$parser ) {
// Create a function hook associating the "example" magic word with renderExample()
$parser->setFunctionHook( 'annot', 'YATA::renderStartAnnot' );
$parser->setFunctionHook( 'annotend', 'YATA::renderEndAnnot' );
$parser->setFunctionHook( 'annotlist', 'YATA::list_annotations' );
}
// Render the output of {{#annot: comment | category | id}}.
public static function renderStartAnnot( $parser, $comment='', $category='', $id='' ) {
return "<span style='background: rgba(0,0,0,0.05)' title='$comment'>comment=$comment, id=$id, category=$category";
}
// Render the output of {{#annotend:}}.
public static function renderEndAnnot( $parser, $id='' ) {
$dbr = wfGetDB( DB_REPLICA );
$str = "";
$result = $dbr->select(
'yata_annotation',
'wiki_text',
''
);
$cat = self::get_category($dbr, 'examples', 'written addition');
#$cat = self::get_category($dbr, 'addition', null);
$str .= $cat->parent . ':' . $cat->name;
return "</span> : $str *---";
}
// Render the output of {{#annotlist:}}.
public static function list_annotations( $parser ) {
$dbr = wfGetDB( DB_REPLICA );
}
/*
Hook on when page is saved
we change the content where necessary, e.g.
- change {{#annotend}} to {{#annotend:}}
- add errors as hidden text at the beginning of the wikitext
*/
/*
1. get all existing annotations which exist on that page.
2. find out which annotations have been deleted, added.
3. updating is possible when an identifier is present (optimization step)
4. assign every annotation to one or more categories
5. assign every annotation to this page
*/
public static function onPageContentSave(WikiPage &$wikiPage, User &$user, Content &$content, $summary, $isMinor, $isWatch, $section, $flags, Status &$status ){
# get database handler
$dbw = wfGetDB( DB_MASTER );
$dbr = wfGetDB( DB_REPLICA );
if ( $content instanceof TextContent ) {
$data = $content->getNativeData();
# replace all convenience {{#annotend}} or {{annotend}} with {{#annotend:}}, where necessary
$data = preg_replace( '/{{#*annotend}}/U','{{#annotend:}}', $data);
$content = new WikitextContent( $data );
# delete all existing annotations
self::delete_annotations($dbw, $wikiPage);
self::delete_annotation_categories($dbw, $wikiPage);
# get all annotations that exist in the current wikitext
$annotations = self::parse_annotations($dbw, $data, $wikiPage, $user);
foreach ( $annotations as $annotation ){
# write annotation
$dbw->insert(
'yata_annotation',
array(
comment => $annotation['comment'],
wiki_text => $annotation['wiki_text'],
start_char => $annotation['start'],
end_char => $annotation['end'],
page_id => $wikiPage->getId(),
user_id => $user->getId()
)
);
# get the last value of the autoincrement field "id".
$annotation_id = $dbw->insertId();
foreach ( $annotation['categories'] as $cat ){
# get parent and child category
$cats = preg_split("/[:]/", $cat);
$parent_cat = $cats[0];
$child_cat = $cats[1];
/*
parent_category:child_category
:child_category
child_category
*/
if (is_null($child_cat)) {
$child_cat = $parent_cat;
$parent_cat = null;
}
$category = self::get_category($dbr, $child_cat, $parent_cat);
# assign all found categories to this annotation
if(! is_null($category)) {
self::insert_annotation_category($dbw, $wikiPage->getId(), $annotation_id, $category->id);
}
}
}
}
}
public static function get_category($dbr, $category, $parent) {
if (is_null($parent)) {
# search for a top category (no parent)
$row = $dbr->selectRow(
'yata_category',
array( 'id', 'name', 'description', 'parent_id' ),
array(
name => $category,
parent_id => $parent
)
);
return $row;
}
else {
# search for category where parent matches
# by self-joining table via parent_id
$row = $dbr->selectRow(
array(y1=>'yata_category', y2=>'yata_category'),
array(
id => 'y1.id',
name => 'y1.name',
description => 'y1.description',
"parent" => 'y2.name',
parent_id => 'y1.parent_id'
),
array(
'y1.name' => $category,
'y2.name' => $parent
),
__METHOD__,
array(),
array( 'y2' => array( 'INNER JOIN', array ( 'y1.parent_id = y2.id' ) ) )
);
return $row;
}
}
public static function delete_annotations($dbw, $wikiPage) {
$dbw->delete(
'yata_annotation',
array(
page_id => $wikiPage->getId()
)
);
}
public static function delete_annotation_categories($dbw, $wikiPage) {
$dbw->delete(
'yata_annotation_category',
array(
page_id => $wikiPage->getId()
)
);
}
public static function insert_annotation_category($dbw, $page_id, $annotation_id, $category_id) {
$dbw->insert(
'yata_annotation_category',
array(
page_id => $page_id,
annotation_id => $annotation_id,
category_id => $category_id
)
);
}
public static function register_error($dbw, $page_id, $error_msg, $location_char) {
$dbw->insert(
'yata_annotation_error',
array(
page_id => $page_id,
error => $error_msg,
location_char => $location_char
)
);
}
public static function delete_errors($dbw, $wikiPage) {
$dbw->delete(
'yata_annotation_error',
array(
page_id => $wikiPage->getId()
)
);
}
public static function parse_annotations($dbw, $data, $wikiPage, $user){
# delete all existing errors
self::delete_errors($dbw, $wikiPage);
$annotations = [];
$annotation_ends = [];
$ends_with_id = [];
$ends_without_id = [];
# lookup all ending annotations (with or without ID)
$end_annot_found = preg_match_all(
'/(?P<annotend>{{#annotend:\s*(?P<id>.*?)\s*}})/s',
$data,
$end_annot,
PREG_OFFSET_CAPTURE
);
if ($end_annot_found) {
foreach ($end_annot["annotend"] as $match) {
$id_found = preg_match('/{{#annotend:\s*(?P<id>.*)}}/s', $match[0], $ids);
# annotation endings with ID
if ($ids["id"]) {
$ends_with_id[$ids["id"]] = $match[1];
}
# annotation endings without ID
else {
$ends_without_id[$match[1]] = true;
}
}
}
# find out where annotations start
$annot_found = preg_match_all('/(?P<annot>{{#annot:.*?}})/s', $data, $annot, PREG_OFFSET_CAPTURE);
if ($annot_found) {
foreach ($annot["annot"] as $match) {
# extract the parameters in each annotation
$params_found = preg_match_all('/{{#annot:\s*(?P<params>.*)}}/s', $match[0], $params);
if ($params_found) {
# {{#annot: $comment | $category | $id}}
list($comment, $category, $id) = explode('|', $params["params"][0]);
# more than one category can be split using a comma: category1, category2
# categories are by default hierarchically organized and must appear
# in following formats:
# parent_category:child_category
# :top_category
# top_category
$categories = preg_split("/[,]/", $category);
foreach ($categories as &$cat){
$cat = trim($cat);
}
$comment = trim($comment);
$id = trim($id);
# determine the start of our annotation
$start = $match[1] + strlen($match[0]);
$annotations[$match[1]] = [
"comment" => $comment,
"categories"=> $categories,
"id" => $id,
"start" => $start,
];
if ($id ) {
# we have an annotation id - we need to connect it to
# its corresponding end
if ($end = $ends_with_id[$id]) {
$annotations[$match[1]]["end"] = $end;
$annotations[$match[1]]["wiki_text"] =
substr($data, $start, $end - $start);
}
else {
# we didn't find any corresponding end: throw an error
self::register_error($dbw, "no end found for id: $id", $start);
}
}
else {
# look for annotation ends without an id
# which are nearest of the start of our annotation
foreach($ends_without_id as $annot_end => $value){
if($annot_end > $start) {
$annotations[$match[1]]["end"] = $annot_end;
$annotations[$match[1]]["wiki_text"] =
substr($data, $start, $annot_end - $start);
break;
}
}
}
}
}
}
return $annotations;
}
// Hook after page is saved:
public static function onPageContentSaveComplete( $article, $user, $content, $summary, $isMinor, $isWatch, $section, $flags, $revision, $status, $baseRevId ) {
}
public static function onLoadExtensionSchemaUpdates( DatabaseUpdater $updater ) {
$updater->addExtensionTable( 'yata_annotation', __DIR__ . '/sql/yata_annotation.sql');
$updater->addExtensionTable( 'yata_category', __DIR__ . '/sql/yata_category.sql');
$updater->addExtensionTable( 'yata_annotation_category', __DIR__ . '/sql/yata_annotation_category.sql');
$updater->addExtensionTable( 'yata_annotation_error', __DIR__ . '/sql/yata_annotation_error.sql');
}
}
<?php
/**
* @since 1.0.0
*
* @file
*
* @licence GNU GPL
* @author Matt Baker (mabaker)
*/
$magicWords = array();
/** English
* @author Matt Baker (mabaker)
*/
$magicWords['en'] = array(
'annot' => array( 0, 'annot' ),
'annotend' => array( 0, 'annotend' ),
'annotlist' => array( 0, 'annotlist' ),
);
{
"name": "YATA",
"author": "Swen Vermeul",
"version": "0.1.0",
"url": "https://www.mediawiki.org/wiki/Extension:YATA",
"descriptionmsg": "YATA - Yet Another Text Annotation",
"license-name": "GPL-2.0+",
"type": "parserhook",
"AutoloadClasses": {
"YATA": "YATA.class.php"
},
"ExtensionMessagesFiles": {
"YATA": "YATA.i18n.php"
},
"Hooks": {
"LoadExtensionSchemaUpdates": [
"YATA::onLoadExtensionSchemaUpdates"
],
"ParserFirstCallInit": [
"YATA::onParserSetup"
],
"PageContentSave": [
"YATA::onPageContentSave"
]
},
"manifest_version": 1
}
BEGIN;
--
-- Tables for the YATA extension
--
-- Annotations table
CREATE TABLE /*_*/yata_annotation (
-- unique identifier for every annotation
id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-- the comment itself.
comment blob,
-- the wiki text which is annotated
wiki_text blob,
-- start and end position of the annotation according to the wiki sourcecode
start_char int unsigned,
end_char int unsigned,
-- foreign key to page.page_id
page_id int unsigned NOT NULL,
-- foreign key to user.user_id
user_id int unsigned NOT NULL
)/*$wgDBTableOptions*/;
CREATE INDEX /*i*/yata_annot_page ON /*_*/yata_annotation (page_id);
CREATE INDEX /*i*/yata_annot_user ON /*_*/yata_annotation (user_id);
COMMIT;
BEGIN;
--
-- Tables for the YATA extension
--
-- Many to many table for annotation and category.
-- Any given annotation can be assigned to more than one category.
CREATE TABLE /*_*/yata_annotation_category (
-- foreign key to a page (for faster lookups)
page_id int unsigned NOT NULL,
-- foreign key to an annotation
annotation_id int unsigned NOT NULL,
-- foreign key to a category
category_id int unsigned NOT NULL
)/*$wgDBTableOptions*/;
CREATE INDEX /*i*/yata_ann_cat_page_idx ON /*_*/yata_annotation_category (page_id);
CREATE INDEX /*i*/yata_ann_cat_annotation_idx ON /*_*/yata_annotation_category (annotation_id);
CREATE INDEX /*i*/yata_ann_cat_category_idx ON /*_*/yata_annotation_category (category_id);
COMMIT;
BEGIN;
--
-- Tables for the YATA extension
--
-- Annotations table
CREATE TABLE /*_*/yata_annotation_error (
-- unique identifier for every annotation
id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-- the syntax error which occured while parsing the text
error blob,
-- foreign key to page.page_id
page_id int unsigned NOT NULL
)/*$wgDBTableOptions*/;
CREATE INDEX /*i*/yata_annot_page ON /*_*/yata_annotation_error (page_id);
COMMIT;
BEGIN;
--
-- Tables for the YATA extension
--
-- Annotation Category table
-- Annotation category table
CREATE TABLE /*_*/yata_category (
-- unique identifier for every annotation category
id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
name varchar(255),
description blob,
parent_id int unsigned
);
CREATE INDEX /*i*/yata_category_name ON /*_*/yata_category (name);
CREATE INDEX /*i*/yata_category_parent ON /*_*/yata_category (parent_id);
COMMIT;
<?php
$blas = preg_split("/[:]/", "child");
print("parent: " .$blas[0]. "\n");
print("child: " . $blas[1] . "\n");
exit;
die();
$data = <<<'EOT'
"Blalablaljka fjlsd {{#annot: here comes a long comment
which spans
over multiple lines | :there }}fjlsdkfj s {{#annot: Kommentar 1 | KatParent:KatChild | eins}}AAsdjf jslajflsfj {{#annot: Kommentar 2 | KatParent2:KatChild2 | zwei}}AAlsjdflksd jflksdj flaskfjZZ{{#annotend: eins}} asöljfölsajöljsdlfjsdlf jsldk fjlskjf asklf jZZ{{#annotend: zwei}} blödesiech sklfjsdf s{{#annotend:}}dlkjfsldjfslkdfj sdlkjfl ksdjfsld fjlsdkf j {{annotend}}
EOT;
#preg_match_all('/(?<all>{{#annot:(?<comment>.*?)|(?<group>.*)|(?<id>)}})/', $data, $matches, PREG_OFFSET_CAPTURE);
# look where annotation starts. group and id might be optional. If id is optional, we will match it to the closest #annotend with no id either.
# We will add an id automatically.
#$matches_found = preg_match_all('/(?P<annot>{{#annot:\s*(?P<comment>.*?)\s*\|\s*(?P<group>.*?)\s*\|\s*(?P<id>.*?)\s*}})/', $data, $matches, PREG_OFFSET_CAPTURE);
# replace all {{#annotend}} or {{annotend}} with {{#annotend:}}, if necessary
$data = preg_replace( '/{{#*annotend}}/U','{{#annotend:}}', $data);
# look where annotation ends
$end_annot_found = preg_match_all('/(?P<annotend>{{#annotend:\s*(?P<id>.*?)\s*}})/s', $data, $end_annot, PREG_OFFSET_CAPTURE);
$annot_found = preg_match_all('/(?P<annot>{{#annot:.*?}})/s', $data, $annot, PREG_OFFSET_CAPTURE);
$annotations = [];
$annotation_ends = [];
$ends_with_id = [];
$ends_without_id = [];
if ($end_annot_found) {
foreach ($end_annot["annotend"] as $match) {
$id_found = preg_match('/{{#annotend:\s*(?P<id>.*)}}/s', $match[0], $ids);
if ($ids["id"]) {
$ends_with_id[$ids["id"]] = $match[1];
}
else {
$ends_without_id[$match[1]] = true;
}
}
}
print("\n----------------- WIKITEXT--------------------------------\n");
print($data);
print("\n----------------------------------------------------------\n");
if ($annot_found) {
foreach ($annot["annot"] as $match) {
$params_found = preg_match_all('/{{#annot:\s*(?P<params>.*)}}/s', $match[0], $params);
if ($params_found) {
list($comment, $category, $id) = explode('|', $params["params"][0]);
$categories = preg_split("/[,;:]/", $category);
foreach ($categories as &$cat){
$cat = trim($cat);
}
#print(var_dump($categories));
$comment = trim($comment);
$category = trim($category);
$id = trim($id);
$start = $match[1] + strlen($match[0]);
$annotations[$match[1]] = [
"comment" => $comment,
"category"=> $categories,
"id" => $id,
"start" => $start,
];
if ($id ) {
# we have an annotation id - we need to connect it to
# its corresponding end
if ($end = $ends_with_id[$id]) {
$annotations[$match[1]]["end"] = $end;
$annotations[$match[1]]["wikitext"] =
substr($data, $start, $end - $start);
}
else {
# we didn't find any corresponding end: throw an error
die("no end found for id: $id");
}
}
else {
# look for annotation ends without an id
# which are nearest of the start of our annotation
foreach($ends_without_id as $annot_end => $value){
if($annot_end > $start) {
$annotations[$match[1]]["end"] = $annot_end;
$annotations[$match[1]]["wikitext"] =
substr($data, $start, $annot_end - $start);
break;
}
}
}
}
}
}
print_r($annotations);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment