) will be sorted instead
23 | * of the itself.
24 | */
25 | jQuery.fn.sortElements = (function() {
26 | var sort = [].sort;
27 | return function(comparator, getSortable) {
28 | getSortable = getSortable || function() { return this; };
29 | var placements = this.map(function() {
30 | var sortElement = getSortable.call(this),
31 | parentNode = sortElement.parentNode,
32 |
33 | // Since the element itself will change position, we have
34 | // to have some way of storing it's original position in
35 | // the DOM. The easiest way is to have a 'flag' node:
36 | nextSibling = parentNode.insertBefore(
37 | document.createTextNode(''),
38 | sortElement.nextSibling
39 | );
40 |
41 | return function() {
42 | if (parentNode === this) {
43 | throw new Error(
44 | "You can't sort elements if any one is a descendant of another."
45 | );
46 | }
47 | // Insert before flag:
48 | parentNode.insertBefore(this, nextSibling);
49 | // Remove flag:
50 | parentNode.removeChild(nextSibling);
51 | };
52 | });
53 | return sort.call(this, comparator).each(function(i) {
54 | placements[i].call(getSortable.call(this));
55 | });
56 | };
57 | })();
58 |
59 | (function() {
60 | // natural compare
61 | var natcmp = function(s1, s2) {
62 | // 'normalize' the values we're sorting
63 | s1 = s1.replace( /<.*?>/g, "" ).replace('>','<').replace('<','>').replace('&','&');
64 | s2 = s2.replace( /<.*?>/g, "" ).replace('>','<').replace('<','>').replace('&','&');
65 |
66 | // do the actual sorting
67 | var n = /^(\d+)(.*)$/;
68 | while (true) {
69 | if (s1 == s2) { return 0; }
70 | if (s1 == '') { return -1; }
71 | if (s2 == '') { return 1; }
72 | var n1 = n.exec(s1);
73 | var n2 = n.exec(s2);
74 | if ( (n1 != null) && (n2 != null) ) {
75 | if (n1[1] != n2[1]) { return n1[1] - n2[1]; }
76 | s1 = n1[2];
77 | s2 = n2[2];
78 | } else {
79 | n1 = s1.charCodeAt(0);
80 | n2 = s2.charCodeAt(0);
81 | if (n1 != n2) { return n1 - n2; }
82 | s1 = s1.substr(1);
83 | s2 = s2.substr(1);
84 | }
85 | }
86 | };
87 | // natural compare right to left (numbers still left to right)
88 | var natcmp_rtl = function(s1, s2) {
89 | // 'normalize' the values we're sorting
90 | s1 = s1.replace( /<.*?>/g, "" ).replace('>','<').replace('<','>').replace('&','&');
91 | s2 = s2.replace( /<.*?>/g, "" ).replace('>','<').replace('<','>').replace('&','&');
92 |
93 | // do the actual sorting
94 | var n = /^(.*?)(\d+)$/;
95 | while (true) {
96 | if (s1 == s2) { return 0; }
97 | if (s1 == '') { return -1; }
98 | if (s2 == '') { return 1; }
99 | var n1 = n.exec(s1);
100 | var n2 = n.exec(s2);
101 | if ( (n1 != null) && (n2 != null) ) {
102 | if (n1[2] != n2[2]) { return n1[2] - n2[2]; }
103 | s1 = n1[1];
104 | s2 = n2[1];
105 | } else {
106 | n1 = s1.charCodeAt(s1.length - 1);
107 | n2 = s2.charCodeAt(s2.length - 1);
108 | if (n1 != n2) { return n1 - n2; }
109 | s1 = s1.substr(0, s1.length - 1);
110 | s2 = s2.substr(0, s2.length - 1);
111 | }
112 | }
113 | };
114 |
115 | // generic stable unique function
116 | var unique = function(es) {
117 | var temp = {};
118 | var result = [];
119 | for(var i = 0; i < es.length; i++) {
120 | var e = es[i];
121 | if(! (e in temp)) {
122 | result.push(e);
123 | temp[e]=true;
124 | }
125 | }
126 |
127 | return result;
128 | };
129 |
130 | // multi field compare
131 | var create_item_compare = function(fields, isAscending, sortType) {
132 | return function(item1, item2) {
133 | var valueMap1 = jQuery(item1).data('strata-item-values');
134 | var valueMap2 = jQuery(item2).data('strata-item-values');
135 | for (var i = 0; i < fields.length; i++) {
136 | var d = isAscending[i] ? 1 : -1;
137 | var cmp = (sortType[i] == 'r' ? natcmp_rtl : natcmp);
138 | var values1 = valueMap1[fields[i]];
139 | var values2 = valueMap2[fields[i]];
140 | var length = Math.min(values1.length, values2.length);
141 | for (var j = 0; j < length; j++) {
142 | var c = cmp(values1[j], values2[j]);
143 | if (c != 0) {
144 | return d * c;
145 | }
146 | }
147 | if (values1.length > values2.length) {
148 | return d * 1;
149 | } else if (values1.length < values2.length) {
150 | return d * -1;
151 | }
152 | }
153 | return parseInt(jQuery(item1).attr('data-strata-order')) - parseInt(jQuery(item2).attr('data-strata-order'));
154 | }
155 | };
156 |
157 | // Create a filter field of the given type and add it to the given filterElement
158 | var createFilterFieldAndSort = function(filterElement, filterType, filterId, field, sortType, fieldSelector, containerElement, caption, minWidth) {
159 | createItemFilterAndSort(containerElement, filterId, field, fieldSelector, filterType);
160 | if (filterType == 't') {
161 | var input = createFilterTextField(containerElement, filterId, caption);
162 | if (minWidth != undefined) {
163 | jQuery(input).css('min-width', minWidth + 'px');
164 | }
165 | jQuery(filterElement).append(input);
166 | } else if (filterType == 's' || filterType == 'p' || filterType == 'e') {
167 | var cmp = (sortType == 'r' ? natcmp_rtl : natcmp);
168 | var select = createFilterSelect(containerElement, filterId, fieldSelector, caption, cmp);
169 | jQuery(filterElement).append(select);
170 | }
171 | };
172 |
173 | // Returns a text input which filters the field belonging to the given filterId
174 | var createFilterTextField = function(element, filterId, caption) {
175 | var input = document.createElement('input');
176 | input.type = 'text';
177 | input.size = 1;
178 | input.title = 'Filter on ' + caption;
179 | jQuery(input).keyup(function() {
180 | var val = jQuery(this).val();
181 | if(val == '') {
182 | delete jQuery(element).data('strata-search')[filterId];
183 | } else {
184 | jQuery(element).data('strata-search')[filterId] = val.toLowerCase();
185 | }
186 | strataFilter(element);
187 | toggleFiltered(this);
188 | });
189 | return input;
190 | };
191 |
192 | // Returns a select input which filters the field belonging to the given filterId
193 | var createFilterSelect = function(element, filterId, fieldSelector, caption, cmp) {
194 | var select = document.createElement('select');
195 | jQuery(select).append(' ');
196 | var values = [];
197 | jQuery(fieldSelector, element).each(function(_,es) {
198 | var vs = jQuery('*.strata-value', es);
199 | if (vs.length) {
200 | vs.each(function(i, v) {
201 | if (values.indexOf(v.textContent) == -1) {
202 | values.push(v.textContent);
203 | }
204 | });
205 | } else if (values.indexOf('') == -1) {
206 | values.push('');
207 | }
208 | });
209 | values.sort(cmp);
210 |
211 | jQuery.each(values, function(_,v) {
212 | var option = document.createElement('option');
213 | option.value = v;
214 | option.textContent = v==''?'':v;
215 | if (v == '') {
216 | option.className = 'strata-filter-special';
217 | }
218 | jQuery(select).append(option);
219 | });
220 |
221 | jQuery(select).change(function() {
222 | var $option = jQuery(this).find(':selected');
223 | if($option.attr('data-filter') == 'none') {
224 | delete jQuery(element).data('strata-search')[filterId];
225 | } else {
226 | jQuery(element).data('strata-search')[filterId] = jQuery(this).val().toLowerCase();
227 | }
228 | strataFilter(element);
229 | toggleFiltered(this);
230 | });
231 | return select;
232 | };
233 |
234 | // Create a filter for every item of the field belonging to the given filterId
235 | var createItemFilterAndSort = function(element, filterId, field, fieldSelector, filterType) {
236 | jQuery('*.strata-item', element).each(function(i, item) {
237 | var values = getValues(item, fieldSelector);
238 |
239 | // Create filter
240 | var filter;
241 | if (filterType == 't') { // substring
242 | // must match at least one value
243 | filter = function(search) {
244 | var result = false;
245 | for (var k = 0; !result && k < values.length; k++) {
246 | result = values[k].indexOf(search) != -1;
247 | }
248 | return result;
249 | };
250 | } else if (filterType == 'p') { // prefix
251 | // must match at least one value
252 | filter = function(search) {
253 | if (search == '') return jQuery.inArray('', values) != -1; // Filtering for empty prefix is useless, so do exact match
254 | var result = false;
255 | for (var k = 0; !result && k < values.length; k++) {
256 | result = values[k].substr(0, search.length) == search;
257 | }
258 | return result;
259 | };
260 | } else if (filterType == 'e') { // ending a.k.a. suffix
261 | // must match at least one value
262 | filter = function(search) {
263 | if (search == '') return jQuery.inArray('', values) != -1; // Filtering for empty suffix is useless, so do exact match
264 | var result = false;
265 | for (var k = 0; !result && k < values.length; k++) {
266 | result = values[k].substr(values[k].length - search.length, search.length) == search;
267 | }
268 | return result;
269 | };
270 | } else { // exact
271 | // must match at least one value
272 | filter = function(search) {
273 | return jQuery.inArray(search, values) != -1;
274 | };
275 | }
276 | addToItemMap(item, 'strata-item-values', field, values);
277 | addToItemMap(item, 'strata-item-filter', filterId, filter);
278 | });
279 | };
280 |
281 | // Get all values for the fields selected by fieldSelector within the given item
282 | function getValues(item, fieldSelector) {
283 | // Return all values of each field and the empty string for fields without values
284 | return jQuery(fieldSelector, item).map(function(_, es) {
285 | var vs = jQuery('*.strata-value', es);
286 | if (vs.length) {
287 | return jQuery.makeArray(vs.map(function(_, v) {
288 | return v.textContent.toLowerCase();
289 | }));
290 | } else {
291 | return '';
292 | }
293 | });
294 | }
295 |
296 | // Store data of the given field for the given item
297 | var addToItemMap = function(item, key, id, values) {
298 | var valueMap = jQuery(item).data(key);
299 | if (valueMap == undefined) {
300 | valueMap = {};
301 | jQuery(item).data(key, valueMap);
302 | }
303 | valueMap[id] = values;
304 | };
305 |
306 | var sortGeneric = function(element, fieldlist) {
307 | var fields = [];
308 | var isAscending = [];
309 | var sortType = [];
310 | var items = jQuery('li', fieldlist);
311 | for (var i = 0; i < items.length && jQuery(items[i]).attr('data-field') != undefined; i++) {
312 | fields.push(jQuery(items[i]).attr('data-field'));
313 | isAscending.push(jQuery('.strata-ui-sort-direction', items[i]).attr('data-strata-sort-direction') == 'asc');
314 | sortType.push(jQuery(items[i]).data('strata-sort-type'));
315 | }
316 | jQuery('.strata-item', element).sortElements(create_item_compare(fields, isAscending, sortType));
317 | };
318 |
319 | var sortTable = function(element, field, isAdditional) {
320 | var fields = jQuery(element).data('strata-sort-fields');
321 | var isAscending = jQuery(element).data('strata-sort-directions');
322 | var sortType = [];
323 | if (fields[0] == field) {
324 | if (isAscending[0]) { // Change sort direction
325 | isAscending[0] = false;
326 | } else { // Remove from sort
327 | fields.splice(0, 1);
328 | isAscending.splice(0, 1);
329 | }
330 | } else if (isAdditional) { // Add as sort field
331 | var i = fields.indexOf(field);
332 | if (i >= 0) {
333 | fields.splice(i, 1);
334 | isAscending.splice(i, 1);
335 | }
336 | fields.unshift(field);
337 | isAscending.unshift(true);
338 | } else { // Replace sort with given field
339 | fields.splice(0, fields.length, field);
340 | isAscending.splice(0, fields.length, true);
341 | }
342 | var sort = jQuery(element).attr('data-strata-ui-sort');
343 | jQuery('th', element).removeAttr('data-strata-sort').removeAttr('data-strata-sort-direction');
344 | jQuery('td', element).removeAttr('data-strata-sort').removeAttr('data-strata-sort-direction');
345 | for (var i = 0; i < fields.length; i++) {
346 | var col = fields[i];
347 | jQuery('.col' + col, element).attr('data-strata-sort', i);
348 | jQuery('.col' + col, element).attr('data-strata-sort-direction', isAscending[i] ? 'asc' : 'desc');
349 | sortType.push(sort[col]);
350 | }
351 | jQuery('.strata-item', element).sortElements(create_item_compare(fields, isAscending, sortType));
352 | };
353 |
354 | // UI initialization
355 | jQuery(document).ready(function() {
356 | // Table UI initialization
357 | jQuery('div.strata-container-table[data-strata-ui-ui="table"]').each(function(i, div) {
358 | // Do not make this a dataTable if a colspan is used somewhere (Colspans are only generated by strata when errors occur)
359 | if (jQuery('table tbody td[colspan][colspan != 1]', div).length > 0) {
360 | return;
361 | }
362 |
363 | // Set filter to empty set
364 | jQuery(div).data('strata-search', {});
365 |
366 | var filterColumns = jQuery(div).attr('data-strata-ui-filter');
367 | var sortColumns = jQuery(div).attr('data-strata-ui-sort');
368 |
369 | // Create sort and filter fields for each column
370 | var tr = document.createElement('tr');
371 | jQuery(tr).addClass('filter');
372 | var thead = jQuery('thead', div);
373 | var headers = jQuery('tr.row0 th', thead);
374 | headers.each(function(i, td) {
375 | var field = jQuery('.strata-caption', td).attr('data-field');
376 | var th = document.createElement('th'); // Filter field
377 | if (field != undefined) { // Is there a field to sort/filter on?
378 | // Create sort
379 | if (sortColumns.charAt(i) != 'n') {
380 | jQuery(td).addClass('sorting');
381 | jQuery(td).click(function(e) {
382 | sortTable(div, i, e.shiftKey);
383 | });
384 | }
385 | // Create filter
386 | var fieldSelector = '.col' + i + ' *.strata-field';
387 | createFilterFieldAndSort(th, filterColumns.charAt(i), i, i, sortColumns.charAt(i), fieldSelector, div, td.textContent);
388 | }
389 | jQuery(tr).append(th);
390 | });
391 | jQuery(thead).append(tr);
392 |
393 | // Set column widths
394 | jQuery('thead tr.row0 th', div).each(
395 | function(i, th) {
396 | // Set the width of a column to its initial width, which is the width of the widest row.
397 | // This avoids resizing when filtering hides long rows in the table.
398 | var width = jQuery(th).width();
399 | jQuery(th).css('min-width', width + 'px');
400 | }
401 | );
402 |
403 | // Set data for sort
404 | jQuery(div).data('strata-sort-fields', []);
405 | jQuery(div).data('strata-sort-directions', []);
406 |
407 | // Allow switching to alternate table view with the meta key
408 | jQuery(thead).click(function(e) {
409 | if (e.metaKey) {
410 | jQuery(div).toggleClass('strata-ui-filter');
411 | }
412 | });
413 | });
414 |
415 | // Generic UI initialization
416 | jQuery('div.strata-container[data-strata-ui-ui="generic"]').each(function(i, div) {
417 | // Set filter to empty set
418 | jQuery(div).data('strata-search', {});
419 |
420 | var filterColumns = jQuery(div).attr('data-strata-ui-filter');
421 | var sortColumns = jQuery(div).attr('data-strata-ui-sort');
422 |
423 | // Create sort and filter fields for each column
424 | var list = document.createElement('ul');
425 | jQuery(list).addClass('filter')
426 | .mouseenter(function(){ jQuery(div).toggleClass('section_highlight', true); })
427 | .mouseleave(function(){ jQuery(div).toggleClass('section_highlight', false); });
428 |
429 | var li = document.createElement('li');
430 | jQuery(li).addClass('ui-state-highlight strata-ui-eos');
431 | jQuery(li).append(document.createTextNode('End of sort order'));
432 | jQuery(list).append(li);
433 | var lastSortable = li;
434 |
435 | // Collect all sort and filter fields
436 | var fields = {};
437 | var fieldOrder = [];
438 | jQuery('.strata-caption', div).each(function(i, captionElement) {
439 | if (sortColumns.charAt(i) != 'n' || filterColumns.charAt(i) != 'n') {
440 | var field = jQuery(captionElement).attr('data-field');
441 | var minWidth = Math.max.apply(Math, jQuery('*.strata-field[data-field="' + field + '"] .strata-value', div).map(function(_, v) {
442 | return jQuery(v).width();
443 | }));
444 | var f;
445 | if (field in fields) {
446 | f = fields[field];
447 | f.caption.push(captionElement.textContent);
448 | if (f.sortType == 'n') {
449 | f.sortType = sortColumns.charAt(i);
450 | }
451 | f.minWidth = Math.max(f.minWidth, minWidth);
452 | } else {
453 | f = {
454 | 'field': field,
455 | 'caption': [captionElement.textContent],
456 | 'sortType': sortColumns.charAt(i),
457 | 'minWidth': minWidth,
458 | 'filters': []
459 | };
460 | fields[field] = f;
461 | fieldOrder.push(f);
462 | }
463 | if (filterColumns.charAt(i) != 'n') {
464 | f.filters.push(filterColumns.charAt(i));
465 | }
466 | }
467 | });
468 | // Create the collected fields
469 | for (var i = 0; i < fieldOrder.length; i++) {
470 | var f = fieldOrder[i];
471 | var caption = unique(f.caption).join(' / ');
472 | var li = document.createElement('li');
473 | jQuery(li).addClass('ui-state-default');
474 | jQuery(li).attr('data-field', f.field);
475 | jQuery(li).append(document.createTextNode(caption));
476 | var fieldSelector = '*.strata-field[data-field="' + f.field + '"]';
477 | if (f.filters.length) {
478 | jQuery(li).append(' ');
479 | }
480 | for (var j = 0; j < f.filters.length; j++) {
481 | createFilterFieldAndSort(li, f.filters[j], i + '_' + j, f.field, f.sortType, fieldSelector, div, caption, f.minWidth);
482 | }
483 | if (f.sortType != 'n') {
484 | jQuery(li).data('strata-sort-type', f.sortType);
485 | var span = document.createElement('span');
486 | jQuery(span).addClass('strata-ui-sort-direction');
487 | jQuery(span).attr('data-strata-sort-direction', 'asc');
488 | jQuery(span).append(' ');
489 | jQuery(li).append(span);
490 | jQuery(span).click(function(e) {
491 | var dir = jQuery(this).attr('data-strata-sort-direction') == 'asc' ? 'desc' : 'asc';
492 | jQuery(this).attr('data-strata-sort-direction', dir);
493 | sortGeneric(div, list);
494 | });
495 | if (f.filters.length == 0) { // No sort data was stored yet, do it now
496 | jQuery('*.strata-item', div).each(function(i, item) {
497 | addToItemMap(item, 'strata-item-values', f.field, getValues(item, fieldSelector));
498 | });
499 | }
500 | } else {
501 | jQuery(li).append(' ');
502 | }
503 | if (f.sortType == 'n') {
504 | jQuery(li).addClass('strata-no-sort');
505 | jQuery(list).append(li);
506 | } else {
507 | jQuery(lastSortable).after(li);
508 | lastSortable = li;
509 | }
510 | }
511 | jQuery(div).prepend(list);
512 |
513 | // Set data for sort
514 | jQuery(div).data('strata-sort-fields', []);
515 | jQuery(div).data('strata-sort-directions', []);
516 |
517 | jQuery(list).sortable({
518 | items: "li:not(.strata-no-sort)",
519 | placeholder: "ui-state-default ui-state-disabled ui-drop-target",
520 | start: function(e, ui) {
521 | jQuery(ui.placeholder).css('min-width', jQuery(ui.item).width() + 'px');
522 | },
523 | update: function(e, ui) {
524 | sortGeneric(div, list);
525 | }
526 | });
527 | });
528 | });
529 |
530 | // Filter every strata-item in the given element based on its filter
531 | var strataFilter = function(element) {
532 | var search = jQuery(element).data('strata-search');
533 | // Traverse all items (rows) that can be filtered
534 | jQuery('*.strata-item', element).each(function(_, item) {
535 | // Traverse all fields on which a filter is applied, filter must match all fields
536 | var filterMap = jQuery(item).data('strata-item-filter');
537 | var matchesAllFields = true;
538 | for (filterId in search) {
539 | var filter = filterMap[filterId];
540 | if (!filter(search[filterId])) {
541 | matchesAllFields = false;
542 | break;
543 | }
544 | }
545 | jQuery(item).toggleClass('hidden', !matchesAllFields);
546 | });
547 | };
548 |
549 | var toggleFiltered = function(tableElement) {
550 | var tr = jQuery(tableElement).closest('tr.filter');
551 | //console.log(Object.keys(...).length);
552 | var isFiltered = false;
553 | tr.find('input').each(function(_, input) {
554 | isFiltered = isFiltered || (input.value != '');
555 | });
556 | tr.find('select').each(function(_, select) {
557 | isFiltered = isFiltered || (jQuery(select).val() != '');
558 | });
559 | tr.toggleClass('isFiltered', isFiltered);
560 | };
561 |
562 | })();
563 |
--------------------------------------------------------------------------------
/sql/setup-mysql.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE data (
2 | subject TEXT NOT NULL,
3 | predicate TEXT NOT NULL,
4 | object TEXT NOT NULL,
5 | graph TEXT NOT NULL
6 | ) CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ENGINE InnoDB;
7 |
8 | -- index for subject-primary retrieval (index prefixes: s, sp)
9 | CREATE INDEX idx_spo ON data(subject(32), predicate(32), object(32)); -- Prefix length is arbitrary
10 |
11 | -- index for predicate-primary retrieval (i.e. property fetch)
12 | CREATE INDEX idx_pso ON data(predicate(32), subject(32), object(32)); -- Prefix length is arbitrary
13 |
--------------------------------------------------------------------------------
/sql/setup-pgsql.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE data (
2 | subject TEXT NOT NULL,
3 | predicate TEXT NOT NULL,
4 | object TEXT NOT NULL,
5 | graph TEXT NOT NULL
6 | );
7 |
8 | -- index for subject-primary retrieval (index prefixes: s, sp)
9 | CREATE INDEX idx_spo ON data(lower(subject), lower(predicate), lower(object));
10 |
11 | -- index for predicate-primary retrieval (i.e. property fetch)
12 | CREATE INDEX idx_pso ON data(lower(predicate), lower(subject), lower(object));
13 |
--------------------------------------------------------------------------------
/sql/setup-sqlite.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE data (
2 | subject TEXT NOT NULL COLLATE NOCASE,
3 | predicate TEXT NOT NULL COLLATE NOCASE,
4 | object TEXT NOT NULL COLLATE NOCASE,
5 | graph TEXT NOT NULL COLLATE NOCASE
6 | );
7 |
8 | -- index for subject-primary retrieval (index prefixes: s, sp)
9 | CREATE INDEX idx_spo ON data(subject, predicate, object);
10 |
11 | -- index for predicate-primary retrieval (i.e. property fetch)
12 | CREATE INDEX idx_pso ON data(predicate, subject, object);
13 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Styling for error display
3 | */
4 |
5 | .dokuwiki div.strata-debug-message { /* the error message */
6 | color: __text__;
7 | background-image: url(../../images/error.png);
8 | background-repeat: no-repeat;
9 | background-position: 8px 50%;
10 | background-color: #FCC;
11 |
12 | margin: 0;
13 | padding: 0.4em 0.4em 0.4em 32px;
14 |
15 | border: 1px solid;
16 | border-color: #FAA;
17 | border-radius: 5px;
18 | border-top-right-radius: 5px;
19 | border-top-left-radius: 5px;
20 | }
21 |
22 | .dokuwiki div.strata-debug-message.strata-debug-continued { /* error message is followed by something */
23 | border-bottom-right-radius: 0;
24 | border-bottom-left-radius: 0;
25 | }
26 |
27 | .dokuwiki div.strata-debug { /* the line container */
28 | line-height: normal;
29 | font-family: monospace;
30 | background-color: __background_site__;
31 | padding: 0.5em;
32 |
33 | border: 1px solid __border__;
34 | border-top: 0;
35 | border-bottom-right-radius: 5px;
36 | border-bottom-left-radius: 5px;
37 |
38 | box-shadow: 0 0 0.5em __border__ inset;
39 | }
40 | .dokuwiki div.strata-debug div.strata-debug-line { /* normal line */
41 | white-space: pre;
42 | padding: 0 2px 0 2px;
43 | }
44 |
45 | .dokuwiki div.strata-debug div.strata-debug-highlight { /* The highlighted lines container */
46 | background-color: #FCC;
47 | color: __text__;
48 | border: 1px #faa solid;
49 | border-radius: 3px;
50 | }
51 |
52 | /**
53 | * Entry styling
54 | */
55 | .dokuwiki .strata-data-fragment-link-previous {
56 | float: left;
57 | }
58 | .dokuwiki .strata-data-fragment-link-next {
59 | float: right;
60 | }
61 |
62 | /* Column filter style */
63 | .strata-container-table tr.filter input {
64 | background-color: __background__;
65 | border: 1px solid silver;
66 | font-style: italic;
67 | width: 100%;
68 | }
69 |
70 | .strata-container-table tr.filter {
71 | display: none;
72 | }
73 |
74 | .strata-container-table tr.filter.isFiltered, .strata-container.strata-ui-filter table tr.filter {
75 | display: table-row;
76 | }
77 |
78 | /* Sort style */
79 | .strata-ui-sort-direction,
80 | .strata-ui-eos ~ li .strata-ui-sort-direction,
81 | .strata-container-table:hover .sorting {
82 | background-image: url('images/sort_both.png');
83 | }
84 |
85 | .strata-ui-sort-direction[data-strata-sort-direction="asc"],
86 | .strata-container-table .sorting[data-strata-sort-direction="asc"],
87 | .strata-container-table:hover .sorting[data-strata-sort-direction="asc"] {
88 | background-image: url('images/sort_asc.png');
89 | }
90 |
91 | .strata-ui-sort-direction[data-strata-sort-direction="desc"],
92 | .strata-container-table .sorting[data-strata-sort-direction="desc"],
93 | .strata-container-table:hover .sorting[data-strata-sort-direction="desc"] {
94 | background-image: url('images/sort_desc.png');
95 | }
96 |
97 | /* Column sort style */
98 | .strata-container-table thead tr th.sorting {
99 | padding-right: 19px;
100 | cursor: pointer;
101 | background-position: right center;
102 | background-repeat: no-repeat;
103 | }
104 |
105 | .strata-container-table thead tr input,
106 | .strata-container-table thead tr select {
107 | min-width: 100%;
108 | }
109 |
110 | .strata-container-table td[data-strata-sort="0"] {
111 | background-color: #EAEBFF;
112 | }
113 |
114 | .strata-container-table td[data-strata-sort="1"] {
115 | background-color: #F2F3FF;
116 | }
117 |
118 | .strata-container-table td[data-strata-sort="2"] {
119 | background-color: #F9F9FF;
120 | }
121 |
122 | /* Generic sort and filter style */
123 | .strata-ui-sort-direction {
124 | display: inline-block;
125 | width: 19px;
126 | margin-left: 4px;
127 | cursor: pointer;
128 | background-position: center center;
129 | background-repeat: no-repeat;
130 | }
131 |
132 | .strata-ui-eos ~ li .strata-ui-sort-direction {
133 | cursor: auto;
134 | }
135 |
136 | .strata-container ul.filter {
137 | list-style-type: none;
138 | padding: 0;
139 | overflow: auto;
140 | border-radius: 5px;
141 | box-shadow: 0 0 5px __border__ inset;
142 | padding: 5px;
143 | }
144 |
145 | .page .strata-container ul.filter li {
146 | margin: 1.5px 1.5px 1.5px 1.5px;
147 | padding: 4px 4px 4px 4px;
148 | height: 21px;
149 | display: inline-block;
150 | border-radius: 2px;
151 | color: @ini_text;
152 | }
153 |
154 | .strata-container ul.filter li:not(.strata-no-sort):before {
155 | content: "|||| ";
156 | cursor: move;
157 | text-shadow: 1px 1px white;
158 | }
159 |
160 | .strata-container ul.filter li.ui-drop-target:before {
161 | content: "Drop here";
162 | font-style: italic;
163 | padding: 4px 7px 4px 7px;
164 | }
165 |
166 | .strata-container .filter option.strata-filter-special {
167 | font-style: italic;
168 | }
169 |
170 | .strata-container .filter li input,
171 | .strata-container .filter li select {
172 | margin-left: 2px;
173 | }
174 |
--------------------------------------------------------------------------------
/syntax/entry.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 |
9 | if (!defined('DOKU_INC')) die('Meh.');
10 |
11 | /**
12 | * Data entry syntax for dedicated data blocks.
13 | */
14 | class syntax_plugin_strata_entry extends DokuWiki_Syntax_Plugin {
15 | protected static $previewMetadata = array();
16 |
17 | function __construct() {
18 | $this->syntax =& plugin_load('helper', 'strata_syntax');
19 | $this->util =& plugin_load('helper', 'strata_util');
20 | $this->triples =& plugin_load('helper', 'strata_triples');
21 | }
22 |
23 | function getType() {
24 | return 'substition';
25 | }
26 |
27 | function getPType() {
28 | return 'block';
29 | }
30 |
31 | function getSort() {
32 | return 450;
33 | }
34 |
35 | function connectTo($mode) {
36 | if($this->getConf('enable_entry')) {
37 | $this->Lexer->addSpecialPattern(']+?)?(?: *#[^>]*?)?>\s*?\n(?:.*?\n)*?\s*? ',$mode, 'plugin_strata_entry');
38 | }
39 | }
40 |
41 | function handle($match, $state, $pos, Doku_Handler $handler) {
42 | $result = array(
43 | 'entry'=>'',
44 | 'data'=> array(
45 | $this->util->getIsaKey(false) => array(),
46 | $this->util->getTitleKey(false) => array()
47 | )
48 | );
49 |
50 | // allow for preprocessing by a subclass
51 | $match = $this->preprocess($match, $state, $pos, $handler, $result);
52 |
53 | $lines = explode("\n",$match);
54 | $header = trim(array_shift($lines));
55 | $footer = trim(array_pop($lines));
56 |
57 |
58 | // allow subclasses to mangle header
59 | $header = $this->handleHeader($header, $result);
60 |
61 | // extract header, and match it to get classes and fragment
62 | preg_match('/^( +[^#>]+)?(?: *#([^>]*?))?$/', $header, $header);
63 |
64 | // process the classes into triples
65 | foreach(preg_split('/\s+/',trim($header[1])) as $class) {
66 | if($class == '') continue;
67 | $result['data'][$this->util->getIsaKey(false)][] = array('value'=>$class,'type'=>'text', 'hint'=>null);
68 | }
69 |
70 | // process the fragment if necessary
71 | $result['entry'] = $header[2];
72 | $result['position'] = $pos;
73 | if($result['entry'] != '') {
74 | $result['title candidate'] = array('value'=>$result['entry'], 'type'=>'text', 'hint'=>null);
75 | }
76 |
77 | // parse tree
78 | $tree = $this->syntax->constructTree($lines,'data entry');
79 |
80 | // allow subclasses first pick in the tree
81 | $this->handleBody($tree, $result);
82 |
83 | // fetch all lines
84 | $lines = $this->syntax->extractText($tree);
85 |
86 | // sanity check
87 | if(count($tree['cs'])) {
88 | msg(sprintf($this->syntax->getLang('error_entry_block'), ($tree['cs'][0]['tag']?sprintf($this->syntax->getLang('named_group'),utf8_tohtml(hsc($tree['cs'][0]['tag']))):$this->syntax->getLang('unnamed_group')), utf8_tohtml(hsc($result['entry']))),-1);
89 | return array();
90 | }
91 |
92 | $p = $this->syntax->getPatterns();
93 |
94 | // now handle all lines
95 | foreach($lines as $line) {
96 | $line = $line['text'];
97 | // match a "property_type(hint)*: value" pattern
98 | // (the * is only used to indicate that the value is actually a comma-seperated list)
99 | // [grammar] ENTRY := PREDICATE TYPE? '*'? ':' ANY
100 | if(preg_match("/^({$p->predicate})\s*({$p->type})?\s*(\*)?\s*:\s*({$p->any}?)$/",$line,$parts)) {
101 | // assign useful names
102 | list(, $property, $ptype, $multi, $values) = $parts;
103 | list($type,$hint) = $p->type($ptype);
104 |
105 | // trim property so we don't get accidental 'name ' keys
106 | $property = utf8_trim($property);
107 |
108 | // lazy create key bucket
109 | if(!isset($result['data'][$property])) {
110 | $result['data'][$property] = array();
111 | }
112 |
113 | // determine values, splitting on commas if necessary
114 | $values = ($multi == '*') ? explode(',',$values) : array($values);
115 |
116 | // generate triples from the values
117 | foreach($values as $v) {
118 | $v = utf8_trim($v);
119 | if($v == '') continue;
120 | // replace the [[]] quasi-magic token with the empty string
121 | if($v == '[[]]') $v = '';
122 | if(!isset($type) || $type == '') {
123 | list($type, $hint) = $this->util->getDefaultType();
124 | }
125 | $result['data'][$property][] = array('value'=>$v,'type'=>$type,'hint'=>($hint?:null));
126 | }
127 | } else {
128 | msg(sprintf($this->syntax->getLang('error_entry_line'), utf8_tohtml(hsc($line))),-1);
129 | }
130 | }
131 |
132 | // normalize data:
133 | // - Normalize all values
134 | $buckets = $result['data'];
135 | $result['data'] = array();
136 |
137 | foreach($buckets as $property=>&$bucket) {
138 | // normalize the predicate
139 | $property = $this->util->normalizePredicate($property);
140 |
141 | // process all triples
142 | foreach($bucket as &$triple) {
143 | // normalize the value
144 | $type = $this->util->loadType($triple['type']);
145 | $triple['value'] = $type->normalize($triple['value'], $triple['hint']);
146 |
147 | // lazy create property bucket
148 | if(!isset($result['data'][$property])) {
149 | $result['data'][$property] = array();
150 | }
151 |
152 | $result['data'][$property][] = $triple;
153 | }
154 | }
155 |
156 |
157 | // normalize title candidate
158 | if(!empty($result['title candidate'])) {
159 | $type = $this->util->loadType($result['title candidate']['type']);
160 | $result['title candidate']['value'] = $type->normalize($result['title candidate']['value'], $result['title candidate']['hint']);
161 | }
162 |
163 | $footer = $this->handleFooter($footer, $result);
164 |
165 | return $result;
166 | }
167 |
168 | /**
169 | * Handles the whole match. This method is called before any processing
170 | * is done by the actual class.
171 | *
172 | * @param match string the complete match
173 | * @param state the parser state
174 | * @param pos the position in the source
175 | * @param the handler object
176 | * @param result array the result array passed to the render method
177 | * @return a preprocessed string
178 | */
179 | function preprocess($match, $state, $pos, &$handler, &$result) {
180 | return $match;
181 | }
182 |
183 | /**
184 | * Handles the header of the syntax. This method is called before
185 | * the header is handled.
186 | *
187 | * @param header string the complete header
188 | * @param result array the result array passed to the render method
189 | * @return a string containing the unhandled parts of the header
190 | */
191 | function handleHeader($header, &$result) {
192 | // remove prefix and suffix
193 | return preg_replace('/(^$)/','',$header);
194 | }
195 |
196 | /**
197 | * Handles the body of the syntax. This method is called before any
198 | * of the body is handled.
199 | *
200 | * @param tree array the parsed tree
201 | * @param result array the result array passed to the render method
202 | */
203 | function handleBody(&$tree, &$result) {
204 | }
205 |
206 | /**
207 | * Handles the footer of the syntax. This method is called after the
208 | * data has been parsed and normalized.
209 | *
210 | * @param footer string the footer string
211 | * @param result array the result array passed to the render method
212 | * @return a string containing the unhandled parts of the footer
213 | */
214 | function handleFooter($footer, &$result) {
215 | return '';
216 | }
217 |
218 |
219 | protected function getPositions($data) {
220 | global $ID;
221 |
222 | // determine positions of other data entries
223 | // (self::$previewMetadata is only filled if a preview_metadata was run)
224 | if(isset(self::$previewMetadata[$ID])) {
225 | $positions = self::$previewMetadata[$ID]['strata']['positions'];
226 | } else {
227 | $positions = p_get_metadata($ID, 'strata positions');
228 | }
229 |
230 | // only read positions if we have them
231 | if(is_array($positions) && isset($positions[$data['entry']])) {
232 | $positions = $positions[$data['entry']];
233 | $currentPosition = array_search($data['position'],$positions);
234 | $previousPosition = isset($positions[$currentPosition-1])?'data_fragment_'.$positions[$currentPosition-1]:null;
235 | $nextPosition = isset($positions[$currentPosition+1])?'data_fragment_'.$positions[$currentPosition+1]:null;
236 | $currentPosition = 'data_fragment_'.$positions[$currentPosition];
237 | }
238 |
239 | return array($currentPosition, $previousPosition, $nextPosition);
240 | }
241 |
242 | function render($mode, Doku_Renderer $R, $data) {
243 | global $ID;
244 |
245 | if($data == array()) {
246 | return false;
247 | }
248 |
249 | if($mode == 'xhtml' || $mode == 'odt') {
250 | list($currentPosition, $previousPosition, $nextPosition) = $this->getPositions($data);
251 | // render table header
252 | if($mode == 'xhtml') { $R->doc .= ''; }
253 | if($mode == 'odt' && isset($currentPosition) && method_exists ($R, 'insertBookmark')) {
254 | $R->insertBookmark($currentPosition, false);
255 | }
256 | $R->table_open();
257 | $R->tablerow_open();
258 | $R->tableheader_open(2);
259 |
260 | // determine actual header text
261 | $heading = '';
262 | if(isset($data['data'][$this->util->getTitleKey()])) {
263 | // use title triple if possible
264 | $heading = $data['data'][$this->util->getTitleKey()][0]['value'];
265 | } elseif (!empty($data['title candidate'])) {
266 | // use title candidate if possible
267 | $heading = $data['title candidate']['value'];
268 | } else {
269 | if(useHeading('content')) {
270 | // fall back to page title, depending on wiki configuration
271 | $heading = p_get_first_heading($ID);
272 | }
273 |
274 | if(!$heading) {
275 | // use page id if all else fails
276 | $heading = noNS($ID);
277 | }
278 | }
279 | $R->cdata($heading);
280 |
281 | // display a comma-separated list of classes if the entry has classes
282 | if(isset($data['data'][$this->util->getIsaKey()])) {
283 | $R->emphasis_open();
284 | $R->cdata(' (');
285 | $values = $data['data'][$this->util->getIsaKey()];
286 | $this->util->openField($mode, $R, $this->util->getIsaKey());
287 | for($i=0;$icdata(', ');
290 | $type = $this->util->loadType($triple['type']);
291 | $this->util->renderValue($mode, $R, $this->triples, $triple['value'], $triple['type'], $type, $triple['hint']);
292 | }
293 | $this->util->closeField($mode, $R);
294 | $R->cdata(')');
295 | $R->emphasis_close();
296 | }
297 | $R->tableheader_close();
298 | $R->tablerow_close();
299 |
300 | // render a row for each key, displaying the values as comma-separated list
301 | foreach($data['data'] as $key=>$values) {
302 | // skip isa and title keys
303 | if($key == $this->util->getTitleKey() || $key == $this->util->getIsaKey()) continue;
304 |
305 | // render row header
306 | $R->tablerow_open();
307 | $R->tableheader_open();
308 | $this->util->renderPredicate($mode, $R, $this->triples, $key);
309 | $R->tableheader_close();
310 |
311 | // render row content
312 | $R->tablecell_open();
313 | $this->util->openField($mode, $R, $key);
314 | for($i=0;$icdata(', ');
317 | $this->util->renderValue($mode, $R, $this->triples, $triple['value'], $triple['type'], $triple['hint']);
318 | }
319 | $this->util->closeField($mode, $R);
320 | $R->tablecell_close();
321 | $R->tablerow_close();
322 | }
323 |
324 | if($previousPosition || $nextPosition) {
325 | $R->tablerow_open();
326 | $R->tableheader_open(2);
327 | if($previousPosition) {
328 | if($mode == 'xhtml') { $R->doc .= ''; }
329 | $R->locallink($previousPosition, $this->util->getLang('data_entry_previous'));
330 | if($mode == 'xhtml') { $R->doc .= ' '; }
331 | }
332 | $R->cdata(' ');
333 | if($nextPosition) {
334 | if($mode == 'xhtml') { $R->doc .= ''; }
335 | $R->locallink($nextPosition, $this->util->getLang('data_entry_next'));
336 | if($mode == 'xhtml') { $R->doc .= ' '; }
337 | }
338 | $R->tableheader_close();
339 | $R->tablerow_close();
340 | }
341 |
342 | $R->table_close();
343 | if($mode == 'xhtml') { $R->doc .= '
'; }
344 |
345 | return true;
346 |
347 | } elseif($mode == 'metadata' || $mode == 'preview_metadata') {
348 | $triples = array();
349 | $subject = $ID.'#'.$data['entry'];
350 |
351 | // resolve the subject to normalize everything
352 | resolve_pageid(getNS($ID),$subject,$exists);
353 |
354 | $titleKey = $this->util->getTitleKey();
355 |
356 | $fixTitle = false;
357 |
358 | // we only use the title determination if no explicit title was given
359 | if(empty($data['data'][$titleKey])) {
360 | if(!empty($data['title candidate'])) {
361 | // we have a candidate from somewhere
362 | $data['data'][$titleKey][] = $data['title candidate'];
363 | } else {
364 | if(!empty($R->meta['title'])) {
365 | // we do not have a candidate, so we use the page title
366 | // (this is possible because fragments set the candidate)
367 | $data['data'][$titleKey][] = array(
368 | 'value'=>$R->meta['title'],
369 | 'type'=>'text',
370 | 'hint'=>null
371 | );
372 | } else {
373 | // we were added before the page title is known
374 | // however, we do require a page title (iff we actually store data)
375 | $fixTitle = true;
376 | }
377 | }
378 | }
379 |
380 | // store positions information
381 | if($mode == 'preview_metadata') {
382 | self::$previewMetadata[$ID]['strata']['positions'][$data['entry']][] = $data['position'];
383 | } else {
384 | $R->meta['strata']['positions'][$data['entry']][] = $data['position'];
385 | }
386 |
387 | // process triples
388 | foreach($data['data'] as $property=>$bucket) {
389 | $this->util->renderPredicate($mode, $R, $this->triples, $property);
390 |
391 | foreach($bucket as $triple) {
392 | // render values for things like backlinks
393 | $type = $this->util->loadType($triple['type']);
394 | $type->render($mode, $R, $this->triples, $triple['value'], $triple['hint']);
395 |
396 | // prepare triples for storage
397 | $triples[] = array('subject'=>$subject, 'predicate'=>$property, 'object'=>$triple['value']);
398 | }
399 | }
400 |
401 | // we're done if nodata is flagged.
402 | if(!isset($R->info['data']) || $R->info['data']==true) {
403 | // batch-store triples if we're allowed to store
404 | $this->triples->addTriples($triples, $ID);
405 |
406 | // set flag for title addendum
407 | if($fixTitle) {
408 | $R->meta['strata']['fixTitle'] = true;
409 | }
410 | }
411 |
412 | return true;
413 | }
414 |
415 | return false;
416 | }
417 | }
418 |
--------------------------------------------------------------------------------
/syntax/info.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 |
9 | // must be run within Dokuwiki
10 | if (!defined('DOKU_INC')) die('Meh.');
11 |
12 | /**
13 | * Simple plugin to list the available types. This plugin uses
14 | * the same syntax as the info plugin, but only accepts a specific
15 | * info category.
16 | */
17 | class syntax_plugin_strata_info extends DokuWiki_Syntax_Plugin {
18 | public function __construct() {
19 | $this->util =& plugin_load('helper', 'strata_util');
20 | }
21 |
22 | public function getType() {
23 | return 'substition';
24 | }
25 |
26 | public function getPType() {
27 | return 'block';
28 | }
29 |
30 | public function getSort() {
31 | // sort just before info plugin
32 | return 154;
33 | }
34 |
35 |
36 | public function connectTo($mode) {
37 | $this->Lexer->addSpecialPattern('~~INFO:stratatypes~~',$mode,'plugin_strata_info');
38 | $this->Lexer->addSpecialPattern('~~INFO:strataaggregates~~',$mode,'plugin_strata_info');
39 | }
40 |
41 | public function handle($match, $state, $pos, Doku_Handler $handler){
42 | $data = array();
43 | preg_match('/~~INFO:strata(type|aggregate)s~~/',$match, $captures);
44 | list(,$kind) = $captures;
45 |
46 | // get a list of all types...
47 | foreach(glob(DOKU_PLUGIN."*/${kind}s/*.php") as $type) {
48 | if(preg_match("@/([^/]+)/${kind}s/([^/]+)\.php@",$type,$matches)) {
49 | // ...load each type...
50 | switch($kind) {
51 | case 'type': $meta = $this->util->loadType($matches[2])->getInfo(); break;
52 | case 'aggregate': $meta = $this->util->loadAggregate($matches[2])->getInfo(); break;
53 | }
54 |
55 | // ...and check if it's synthetic (i.e., not user-usable)
56 | if(!isset($meta['synthetic']) || !$meta['synthetic']) {
57 | $data[] = array(
58 | 'name'=>$matches[2],
59 | 'plugin'=>$matches[1],
60 | 'meta'=>$meta
61 | );
62 | }
63 | }
64 | }
65 |
66 | usort($data, array($this,'_compareNames'));
67 |
68 | return array($kind,$data);
69 | }
70 |
71 | function _compareNames($a, $b) {
72 | return strcmp($a['name'], $b['name']);
73 | }
74 |
75 | public function render($mode, Doku_Renderer $R, $data) {
76 | if($mode == 'xhtml' || $mode == 'odt') {
77 | list($kind, $items) = $data;
78 |
79 | $R->listu_open();
80 | foreach($items as $data){
81 | $R->listitem_open(1);
82 | $R->listcontent_open();
83 |
84 | $R->strong_open();
85 | $R->cdata($data['name']);
86 | $R->strong_close();
87 |
88 | if($data['meta']['hint']) {
89 | $R->cdata(' ('.$kind.' hint: '. $data['meta']['hint'] .')');
90 | }
91 | // $R->emphasis_open();
92 | // $R->cdata(' in '.$data['plugin'].' plugin');
93 | // $R->emphasis_close();
94 |
95 | $R->linebreak();
96 | $R->cdata($data['meta']['desc']);
97 |
98 | if(isset($data['meta']['tags']) && count($data['meta']['tags'])) {
99 | $R->cdata(' (');
100 | $R->emphasis_open();
101 | $R->cdata(implode(', ',$data['meta']['tags']));
102 | $R->emphasis_close();
103 | $R->cdata(')');
104 | }
105 |
106 | $R->listcontent_close();
107 | $R->listitem_close();
108 | }
109 | $R->listu_close();
110 | return true;
111 | }
112 |
113 | return false;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/syntax/list.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 |
9 | if (!defined('DOKU_INC')) die('Meh.');
10 |
11 | /**
12 | * List syntax for basic query handling.
13 | */
14 | class syntax_plugin_strata_list extends syntax_plugin_strata_select {
15 | function connectTo($mode) {
16 | $this->Lexer->addSpecialPattern('helper->fieldsShortPattern().'* *>\s*?\n.+?\n\s*?
',$mode, 'plugin_strata_list');
17 | }
18 |
19 | function handleHeader($header, &$result, &$typemap) {
20 | return preg_replace('/(^$)/','',$header);
21 | }
22 |
23 | function render($mode, Doku_Renderer $R, $data) {
24 | if($data == array() || isset($data['error'])) {
25 | if($mode == 'xhtml' || $mode == 'odt') {
26 | $R->listu_open();
27 | $R->listitem_open(1);
28 | $R->listcontent_open();
29 | $this->displayError($mode, $R, $data);
30 | $R->listcontent_close();
31 | $R->listitem_close();
32 | $R->listu_close();
33 | }
34 |
35 | return;
36 | }
37 |
38 | $query = $this->prepareQuery($data['query']);
39 |
40 | // execute the query
41 | $result = $this->triples->queryRelations($query);
42 |
43 | if($result == false) {
44 | if($mode == 'xhtml' || $mode == 'odt') {
45 | $R->listu_open();
46 | $R->listitem_open(1);
47 | $R->listcontent_open();
48 | $R->emphasis_open();
49 | $R->cdata(sprintf($this->helper->getLang('content_error_explanation'),'Strata list'));
50 | $R->emphasis_close();
51 | $R->listcontent_close();
52 | $R->listitem_close();
53 | $R->listu_close();
54 | }
55 |
56 | return;
57 | }
58 |
59 | // prepare all 'columns'
60 | $fields = array();
61 | foreach($data['fields'] as $meta) {
62 | $fields[] = array(
63 | 'variable'=>$meta['variable'],
64 | 'caption'=>$meta['caption'],
65 | 'type'=>$this->util->loadType($meta['type']),
66 | 'typeName'=>$meta['type'],
67 | 'hint'=>$meta['hint'],
68 | 'aggregate'=>$this->util->loadAggregate($meta['aggregate']),
69 | 'aggergateHint'=>$meta['aggregateHint']
70 | );
71 | }
72 |
73 |
74 | if($mode == 'xhtml' || $mode == 'odt') {
75 | // render header
76 | $this->ui_container_open($mode, $R, $data, array('strata-container', 'strata-container-list'));
77 |
78 | $this->util->renderCaptions($mode, $R, $fields);
79 |
80 | $R->listu_open();
81 |
82 | // render each row
83 | $itemcount = 0;
84 | foreach($result as $row) {
85 | if($mode == 'xhtml') {
86 | $R->doc .= ''.DOKU_LF;
87 | } else {
88 | $R->listitem_open(1);
89 | }
90 | $R->listcontent_open();
91 |
92 | $fieldCount = 0;
93 |
94 | foreach($fields as $f) {
95 | $values = $f['aggregate']->aggregate($row[$f['variable']], $f['aggregateHint']);
96 | if(!count($values)) continue;
97 | if($fieldCount>1) $R->cdata('; ');
98 | if($fieldCount==1) $R->cdata(' (');
99 | $this->util->renderField($mode, $R, $this->triples, $values, $f['typeName'], $f['hint'], $f['type'], $f['variable']);
100 | $fieldCount++;
101 | }
102 |
103 | if($fieldCount>1) $R->cdata(')');
104 |
105 | $R->listcontent_close();
106 | if($mode == 'xhtml') {
107 | $R->doc.= ' '.DOKU_LF;
108 | } else {
109 | $R->listitem_close();
110 | }
111 | }
112 | $result->closeCursor();
113 |
114 | $R->listu_close();
115 | $this->ui_container_close($mode, $R);
116 |
117 | return true;
118 | } elseif($mode == 'metadata') {
119 | // render all rows in metadata mode to enable things like backlinks
120 | foreach($result as $row) {
121 | foreach($fields as $f) {
122 | $this->util->renderField($mode, $R, $this->triples, $f['aggregate']->aggregate($row[$f['variable']],$f['aggregateHint']), $f['typeName'], $f['hint'], $f['type'], $f['variable']);
123 | }
124 | }
125 | $result->closeCursor();
126 |
127 | return true;
128 | }
129 |
130 | return false;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/syntax/nodata.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 |
9 | // must be run within Dokuwiki
10 | if (!defined('DOKU_INC')) die('Meh.');
11 |
12 | /**
13 | * Simple plugin that sets the 'no data' flag.
14 | */
15 | class syntax_plugin_strata_nodata extends DokuWiki_Syntax_Plugin {
16 | public function __construct() {
17 | }
18 |
19 | public function getType() {
20 | return 'substition';
21 | }
22 |
23 | public function getPType() {
24 | return 'normal';
25 | }
26 |
27 | public function getSort() {
28 | // sort at same level as notoc
29 | return 30;
30 | }
31 |
32 |
33 | public function connectTo($mode) {
34 | $this->Lexer->addSpecialPattern('~~NODATA~~',$mode,'plugin_strata_nodata');
35 | }
36 |
37 | public function handle($match, $state, $pos, Doku_Handler $handler){
38 | return array();
39 | }
40 |
41 | public function render($mode, Doku_Renderer $R, $data) {
42 | if($mode == 'metadata') {
43 | $R->info['data'] = false;
44 | return true;
45 | }
46 |
47 | return false;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/syntax/select.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 |
9 | if(!defined('DOKU_INC')) die('Meh.');
10 |
11 | /**
12 | * Select syntax for basic query handling.
13 | */
14 | class syntax_plugin_strata_select extends DokuWiki_Syntax_Plugin {
15 | function __construct() {
16 | $this->helper =& plugin_load('helper', 'strata_syntax');
17 | $this->util =& plugin_load('helper', 'strata_util');
18 | $this->triples =& plugin_load('helper', 'strata_triples');
19 | }
20 |
21 | function getType() {
22 | return 'substition';
23 | }
24 |
25 | function getPType() {
26 | return 'block';
27 | }
28 |
29 | function getSort() {
30 | return 450;
31 | }
32 |
33 | function connectTo($mode) {
34 | }
35 |
36 | function getUISettings($numFields, $hasUIBlock) {
37 | $sort_choices = array(
38 | 'y' => array('default', 'yes', 'y'),
39 | 'l' => array('left to right', 'ltr', 'l'),
40 | 'r' => array('right to left', 'rtl', 'r'),
41 | 'n' => array('none', 'no', 'n')
42 | );
43 | $filter_choices = array(
44 | 't' => array('text', 't'),
45 | 's' => array('select', 's'),
46 | 'p' => array('prefix select', 'ps'),
47 | 'e' => array('suffix select', 'ss'),
48 | 'n' => array('none', 'no', 'n')
49 | );
50 | $globalProperties = array(
51 | 'ui' => $this->getUISettingUI($hasUIBlock),
52 | 'sort' => array('choices' => $sort_choices, 'minOccur' => $numFields, 'maxOccur' => $numFields, 'default' => 'yes'),
53 | 'filter' => array('choices' => $filter_choices, 'minOccur' => $numFields, 'maxOccur' => $numFields, 'default' => 'none')
54 | );
55 | $groupProperties = array(
56 | 'sort' => array('choices' => $sort_choices),
57 | 'filter' => array('choices' => $filter_choices),
58 | );
59 | return array($globalProperties, $groupProperties);
60 | }
61 |
62 | function getUISettingUI($hasUIBlock) {
63 | return array('choices' => array('none' => array('none', 'no', 'n'), 'generic' => array('generic', 'g')), 'default' => ($hasUIBlock ? 'generic' : 'none'));
64 | }
65 |
66 | function handle($match, $state, $pos, Doku_Handler $handler) {
67 | try {
68 | $result = array();
69 | $typemap = array();
70 |
71 | // allow subclass handling of the whole match
72 | $match = $this->preprocess($match, $state, $pos, $handler, $result, $typemap);
73 |
74 | // split into lines and remove header and footer
75 | $lines = explode("\n",$match);
76 | $header = trim(array_shift($lines));
77 | $footer = trim(array_pop($lines));
78 |
79 | // allow subclass header handling
80 | $header = $this->handleHeader($header, $result, $typemap);
81 |
82 | // parse projection information in 'short syntax' if available
83 | if(trim($header) != '') {
84 | $result['fields'] = $this->helper->parseFieldsShort($header, $typemap);
85 | }
86 |
87 | $tree = $this->helper->constructTree($lines,'query');
88 |
89 | // parse long fields, if available
90 | $longFields = $this->helper->getFields($tree, $typemap);
91 |
92 | // check double data
93 | if(count($result['fields']) && count($longFields)) {
94 | $this->helper->_fail($this->getLang('error_query_bothfields'));
95 | }
96 |
97 | // assign longfields if necessary
98 | if(count($result['fields']) == 0) {
99 | $result['fields'] = $longFields;
100 | }
101 |
102 | // check no data
103 | if(count($result['fields']) == 0) {
104 | $this->helper->_fail($this->helper->getLang('error_query_noselect'));
105 | }
106 |
107 | // determine the variables to project
108 | $projection = array();
109 | foreach($result['fields'] as $f) $projection[] = $f['variable'];
110 | $projection = array_unique($projection);
111 |
112 | // allow subclass body handling
113 | $this->handleBody($tree, $result, $typemap);
114 |
115 | // parse UI group
116 | $this->handleUI($tree, $result, $typemap);
117 |
118 | // parse the query itself
119 | list($result['query'], $variables) = $this->helper->constructQuery($tree, $typemap, $projection);
120 |
121 | // allow subclass footer handling
122 | $footer = $this->handleFooter($footer, $result, $typemap, $variable);
123 |
124 | // check projected variables and load types
125 | foreach($result['fields'] as $i=>$f) {
126 | $var = $f['variable'];
127 | if(!in_array($var, $variables)) {
128 | $this->helper->_fail(sprintf($this->helper->getLang('error_query_unknownselect'),utf8_tohtml(hsc($var))));
129 | }
130 |
131 | if(empty($f['type'])) {
132 | if(!empty($typemap[$var])) {
133 | $result['fields'][$i] = array_merge($result['fields'][$i],$typemap[$var]);
134 | } else {
135 | list($type, $hint) = $this->util->getDefaultType();
136 | $result['fields'][$i]['type'] = $type;
137 | $result['fields'][$i]['hint'] = $hint;
138 | }
139 | }
140 | }
141 |
142 | return $result;
143 | } catch(strata_exception $e) {
144 | return array('error'=>array(
145 | 'message'=>$e->getMessage(),
146 | 'regions'=>$e->getData(),
147 | 'lines'=>$lines
148 | ));
149 | }
150 | }
151 |
152 | function handleUI(&$tree, &$result, &$typemap) {
153 | $trees = $this->helper->extractGroups($tree, 'ui');
154 |
155 | list($globalProperties, $groupProperties) = $this->getUISettings(count($result['fields']), count($trees));
156 |
157 | // Extract column settings which are set as a group
158 |
159 | // Extract named column settings
160 | $namedGroupSettings = array();
161 | foreach ($result['fields'] as $i => $f) {
162 | if(isset($namedGroupSettings[$f['caption']])) continue;
163 | $groups = array();
164 | foreach($trees as &$t) {
165 | $groups = array_merge($groups, $this->helper->extractGroups($t, $f['caption']));
166 | }
167 | $namedGroupSettings[$f['caption']] = $this->helper->setProperties($groupProperties, $groups);
168 | }
169 |
170 | // Extract numbered column settings
171 | $groupsettings = array();
172 | foreach ($result['fields'] as $i => $f) {
173 | $groups = array();
174 | foreach ($trees as &$t) {
175 | $groups = array_merge($groups, $this->helper->extractGroups($t, '#' . ($i+1)));
176 | }
177 |
178 | // process settings for this column
179 | $groupsettings[$i] = $this->helper->setProperties($groupProperties, $groups);
180 |
181 | // fill in unset properties from named settings
182 | foreach($namedGroupSettings[$f['caption']] as $k=>$v) {
183 | if(!isset($groupsettings[$i][$k])) {
184 | $groupsettings[$i][$k] = $v;
185 | }
186 | }
187 | }
188 |
189 | // Extract global settings
190 | $result['strata-ui'] = $this->helper->setProperties($globalProperties, $trees);
191 |
192 | // Merge column settings into global ones
193 | foreach ($groupsettings as $i => $s) {
194 | foreach ($s as $p => $v) {
195 | $result['strata-ui'][$p][$i] = $v[0];
196 | }
197 | }
198 | }
199 |
200 | /**
201 | * Handles the whole match. This method is called before any processing
202 | * is done by the actual class.
203 | *
204 | * @param match string the complete match
205 | * @param state the parser state
206 | * @param pos the position in the source
207 | * @param handler object the parser handler
208 | * @param result array the result array passed to the render method
209 | * @param typemap array the type map
210 | * @return a preprocessed string
211 | */
212 | function preprocess($match, $state, $pos, &$handler, &$result, &$typemap) {
213 | return $match;
214 | }
215 |
216 |
217 | /**
218 | * Handles the header of the syntax. This method is called before
219 | * the header is handled.
220 | *
221 | * @param header string the complete header
222 | * @param result array the result array passed to the render method
223 | * @param typemap array the type map
224 | * @return a string containing the unhandled parts of the header
225 | */
226 | function handleHeader($header, &$result, &$typemap) {
227 | return $header;
228 | }
229 |
230 | /**
231 | * Handles the body of the syntax. This method is called before any
232 | * of the body is handled, but after the 'fields' groups have been processed.
233 | *
234 | * @param tree array the parsed tree
235 | * @param result array the result array passed to the render method
236 | * @param typemap array the type map
237 | */
238 | function handleBody(&$tree, &$result, &$typemap) {
239 | }
240 |
241 | /**
242 | * Handles the footer of the syntax. This method is called after the
243 | * query has been parsed, but before the typemap is applied to determine
244 | * all field types.
245 | *
246 | * @param footer string the footer string
247 | * @param result array the result array passed to the render method
248 | * @param typemape array the type map
249 | * @param variables array of variables used in query
250 | * @return a string containing the unhandled parts of the footer
251 | */
252 | function handleFooter($footer, &$result, &$typemap, &$variables) {
253 | return $footer;
254 | }
255 |
256 | /**
257 | * This method performs just-in-time modification to prepare
258 | * the query for use.
259 | *
260 | * @param query array the query tree
261 | * @return the query tree to use
262 | */
263 | function prepareQuery($query) {
264 | // fire event
265 | trigger_event('STRATA_PREPARE_QUERY', $query);
266 |
267 | // return the (possibly modified) query
268 | return $query;
269 | }
270 |
271 | /**
272 | * This method renders the data view.
273 | *
274 | * @param mode the rendering mode
275 | * @param R the renderer
276 | * @param data the custom data from the handle phase
277 | */
278 | function render($mode, Doku_Renderer $R, $data) {
279 | return false;
280 | }
281 |
282 | /**
283 | * This method renders the container for any strata select.
284 | *
285 | * The open tag will contain all give classes plus additional metadata, e.g., generated by the ui group
286 | *
287 | * @param mode the render mode
288 | * @param R the renderer
289 | * @param data the custom data from the handle phase
290 | * @param additionalClasses array containing classes to be set on the generated container
291 | */
292 | function ui_container_open($mode, &$R, $data, $additionalClasses=array()) {
293 | // only xhtml mode needs the UI container
294 | if($mode != 'xhtml') return;
295 |
296 | $p = $data['strata-ui'];
297 | $c = array();
298 |
299 | // Default sort: rtl for suffix and ltr otherwise
300 | for ($i = 0; $i < count($p['sort']); $i++) {
301 | if ($p['sort'][$i] == 'y') {
302 | $p['sort'][$i] = ($p['filter'][$i] == 'e' ? 'r' : 'l');
303 | }
304 | }
305 |
306 | if (trim(implode($p['sort']), 'n') != '') {
307 | $c[] = 'strata-ui-sort';
308 | }
309 | if (trim(implode($p['filter']), 'n') != '') {
310 | $c[] = 'strata-ui-filter';
311 | }
312 |
313 | $classes = implode(' ', array_merge($c, $additionalClasses));
314 | $properties = implode(' ', array_map(
315 | function($k, $v) {
316 | if (empty($v)) {
317 | return '';
318 | } else {
319 | return 'data-strata-ui-' . $k . '="' . implode($v) . '"';
320 | }
321 | }, array_keys($p), $p)
322 | );
323 |
324 | $R->doc .= '' . DOKU_LF;
325 | }
326 |
327 | function ui_container_close($mode, &$R) {
328 | // only xhtml mode needs the UI container
329 | if($mode != 'xhtml') return;
330 | $R->doc .= '
' . DOKU_LF;
331 | }
332 |
333 | protected function displayError($mode, &$R, $data) {
334 | if($mode == 'xhtml') {
335 | $style = '';
336 | if(isset($data['error']['regions'])) $style = ' strata-debug-continued';
337 | $R->doc .= '';
338 | $R->cdata($this->helper->getLang('content_error_explanation'));
339 | $R->doc .= ': '.$data['error']['message'];
340 | $R->doc .= '
';
341 | if(isset($data['error']['regions'])) $R->doc .= $this->helper->debugTree($data['error']['lines'], $data['error']['regions']);
342 | } elseif($mode == 'odt') {
343 | $R->cdata($this->helper->getLang('content_error_explanation__non_xhtml'));
344 | }
345 | }
346 | }
347 |
--------------------------------------------------------------------------------
/syntax/table.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 |
9 | if (!defined('DOKU_INC')) die('Meh.');
10 |
11 | /**
12 | * Table syntax for basic query handling.
13 | */
14 | class syntax_plugin_strata_table extends syntax_plugin_strata_select {
15 | function connectTo($mode) {
16 | $this->Lexer->addSpecialPattern('helper->fieldsShortPattern().'* *>\s*?\n.+?\n\s*?
',$mode, 'plugin_strata_table');
17 | }
18 |
19 | function getUISettingUI($hasUIBlock) {
20 | return array('choices' => array('none' => array('none', 'no', 'n'), 'generic' => array('generic', 'g'), 'table' => array('table', 't')), 'default' => 'table');
21 | }
22 |
23 | function handleHeader($header, &$result, &$typemap) {
24 | return preg_replace('/(^$)/','',$header);
25 | }
26 |
27 | function render($mode, Doku_Renderer $R, $data) {
28 | if($data == array() || isset($data['error'])) {
29 | if($mode == 'xhtml' || $mode == 'odt') {
30 | $R->table_open();
31 | $R->tablerow_open();
32 | $R->tablecell_open();
33 | $this->displayError($mode, $R, $data);
34 | $R->tablecell_close();
35 | $R->tablerow_close();
36 | $R->table_close();
37 | }
38 | return;
39 | }
40 |
41 | $query = $this->prepareQuery($data['query']);
42 |
43 | // execute the query
44 | $result = $this->triples->queryRelations($query);
45 |
46 | // prepare all columns
47 | foreach($data['fields'] as $meta) {
48 | $fields[] = array(
49 | 'variable'=>$meta['variable'],
50 | 'caption'=>$meta['caption'],
51 | 'type'=>$this->util->loadType($meta['type']),
52 | 'typeName'=>$meta['type'],
53 | 'hint'=>$meta['hint'],
54 | 'aggregate'=>$this->util->loadAggregate($meta['aggregate']),
55 | 'aggregateHint'=>$meta['aggregateHint']
56 | );
57 | }
58 |
59 | if($mode == 'xhtml' || $mode == 'odt') {
60 | // render header
61 | $this->ui_container_open($mode, $R, $data, array('strata-container', 'strata-container-table'));
62 | $R->table_open();
63 | if($mode == 'xhtml') { $R->doc .= ''.DOKU_LF; }
64 | $R->tablerow_open();
65 |
66 | // render all columns
67 | foreach($fields as $f) {
68 | $R->tableheader_open();
69 | if($mode == 'xhtml') { $R->doc .= ''; }
70 | $R->cdata($f['caption']);
71 | if($mode == 'xhtml') { $R->doc .= ' '.DOKU_LF; }
72 | $R->tableheader_close();
73 | }
74 | $R->tablerow_close();
75 | if($mode == 'xhtml') { $R->doc .= ' '.DOKU_LF; }
76 |
77 | if($result != false) {
78 | // render each row
79 | $itemcount = 0;
80 | foreach($result as $row) {
81 | if($mode == 'xhtml') { $R->doc .= ''.DOKU_LF; }
82 | $R->tablerow_open();
83 | foreach($fields as $f) {
84 | $R->tablecell_open();
85 | $this->util->renderField($mode, $R, $this->triples, $f['aggregate']->aggregate($row[$f['variable']],$f['aggregateHint']), $f['typeName'], $f['hint'], $f['type'], $f['variable']);
86 | $R->tablecell_close();
87 | }
88 | $R->tablerow_close();
89 | if($mode == 'xhtml') { $R->doc .= ' '.DOKU_LF; }
90 | }
91 | $result->closeCursor();
92 | } else {
93 | $R->tablerow_open();
94 | $R->tablecell_open(count($fields));
95 | $R->emphasis_open();
96 | $R->cdata(sprintf($this->helper->getLang('content_error_explanation'),'Strata table'));
97 | $R->emphasis_close();
98 | $R->tablecell_close();
99 | $R->tablerow_close();
100 | }
101 |
102 | $R->table_close();
103 | $this->ui_container_close($mode, $R);
104 |
105 | return true;
106 | } elseif($mode == 'metadata') {
107 | if($result == false) return;
108 |
109 | // render all rows in metadata mode to enable things like backlinks
110 | foreach($result as $row) {
111 | foreach($fields as $f) {
112 | $this->util->renderField($mode, $R, $this->triples, $f['aggregate']->aggregate($row[$f['variable']],$f['aggregateHint']), $f['typeName'], $f['hint'], $f['type'], $f['variable']);
113 | }
114 | }
115 | $result->closeCursor();
116 |
117 | return true;
118 | }
119 |
120 | return false;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/types/date.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 | // must be run within Dokuwiki
7 | if(!defined('DOKU_INC')) die('Meh.');
8 |
9 | /**
10 | * The date type.
11 | */
12 | class plugin_strata_type_date extends plugin_strata_type {
13 | function render($mode, &$R, &$triples, $value, $hint) {
14 | if(is_numeric($value)) {
15 | // use the hint if available
16 | $format = $hint ?: 'Y-m-d';
17 |
18 | // construct representation
19 | $date = new DateTime();
20 | $date->setTimestamp((int)$value);
21 |
22 | // render
23 | $R->cdata($date->format($format));
24 | } else {
25 | $R->cdata($value);
26 | }
27 | return true;
28 | }
29 |
30 | function normalize($value, $hint) {
31 | // use hint if available
32 | // (prefix with '!' te reset all fields to the unix epoch)
33 | $format = '!'. ($hint ?: 'Y-m-d');
34 |
35 | // try and parse the value
36 | $date = date_create_from_format($format, $value);
37 |
38 | // handle failure in a non-intrusive way
39 | if($date === false) {
40 | return $value;
41 | } else {
42 | return $date->getTimestamp();
43 | }
44 | }
45 |
46 | function getInfo() {
47 | return array(
48 | 'desc'=>'Stores and displays dates in the YYYY-MM-DD format. The optional hint can give a different format to use.',
49 | 'tags'=>array('numeric'),
50 | 'hint'=>'different date format'
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/types/image.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 | // must be run within Dokuwiki
7 | if(!defined('DOKU_INC')) die('Meh.');
8 |
9 | /**
10 | * The image type.
11 | */
12 | class plugin_strata_type_image extends plugin_strata_type {
13 | function isExternalMedia($value) {
14 | return preg_match('#^(https?|ftp)#i', $value);
15 | }
16 |
17 | function normalize($value, $hint) {
18 | global $ID;
19 |
20 | // strip leading {{ and closing }}
21 | $value = preg_replace(array('/^\{\{/','/\}\}$/u'), '', $value);
22 |
23 | // drop any title and alignment spacing whitespace
24 | $value = explode('|', $value, 2);
25 | $value = trim($value[0]);
26 |
27 | if($this->isExternalMedia($value)) {
28 | // external image
29 | // we don't do anything else here
30 | } else {
31 | // internal image
32 |
33 | // discard size string and other options
34 | $pos = strrpos($value, '?');
35 | if($pos !== false ) {
36 | $value = substr($value, 0, $pos);
37 | }
38 |
39 | // resolve page id with respect to selected base
40 | resolve_mediaid(getNS($ID),$value,$exists);
41 | }
42 |
43 | return $value;
44 | }
45 |
46 | function render($mode, &$R, &$T, $value, $hint) {
47 | if(preg_match('/([0-9]+)(?:x([0-9]+))?/',$hint,$captures)) {
48 | if(!empty($captures[1])) $width = (int)$captures[1];
49 | if(!empty($captures[2])) $height = (int)$captures[2];
50 | }
51 |
52 | if($this->isExternalMedia($value)) {
53 | // render external media
54 | $R->externalmedia($value,null,null,$width,$height);
55 | } else {
56 | // render internal media
57 | // (':' is prepended to make sure we use an absolute pagename,
58 | // internalmedia resolves media ids, but the name is already resolved.)
59 | $R->internalmedia(':'.$value,null,null,$width,$height);
60 | }
61 | }
62 |
63 | function getInfo() {
64 | return array(
65 | 'desc'=>'Displays an image. The optional hint is treated as the size to scale the image to. Give the hint in WIDTHxHEIGHT format.',
66 | 'hint'=>'size to scale the image to'
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/types/link.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 | // must be run within Dokuwiki
7 | if(!defined('DOKU_INC')) die('Meh.');
8 |
9 | /**
10 | * The multi-purpose link type.
11 | */
12 | class plugin_strata_type_link extends plugin_strata_type {
13 | function render($mode, &$renderer, &$triples, $value, $hint) {
14 | if(preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$value)) {
15 | // Interwiki
16 | $interwiki = explode('>',$value,2);
17 | $renderer->interwikilink($value,$hint, strtolower($interwiki[0]), $interwiki[1]);
18 |
19 | } elseif(preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$value)) {
20 | $renderer->windowssharelink($value,$hint);
21 |
22 | } elseif(preg_match('#^([a-z0-9\-\.+]+?)://#i',$value)) {
23 | $renderer->externallink($value,$hint);
24 |
25 | } elseif(preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$value)) {
26 | $renderer->emaillink($value,$hint);
27 |
28 | } else {
29 | $renderer->internallink(':'.$value, $hint);
30 | }
31 |
32 | return true;
33 | }
34 |
35 | function normalize($value, $hint) {
36 | // strip off leading [[ and trailing ]] to offer a more
37 | // user-friendly syntax.
38 | if(substr($value,0,2) == '[[' && substr($value,-2) == ']]') {
39 | $value = substr($value,2,-2);
40 | }
41 |
42 | if(!preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$value)
43 | && !preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$value)
44 | && !preg_match('#^([a-z0-9\-\.+]+?)://#i',$value)
45 | && !preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$value)) {
46 | $page = new plugin_strata_type_page();
47 | return $page->normalize($value,null);
48 | }
49 |
50 | return $value;
51 | }
52 |
53 | function getInfo() {
54 | return array(
55 | 'desc'=>'Creates a link. This type is multi-purpose: it handles external links, interwiki links, email addresses, windows shares and normal wiki links (basically any link DokuWiki knows of). The optional hint will be used as link title.',
56 | 'hint'=>'The link title'
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/types/page.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 | // must be run within Dokuwiki
7 | if(!defined('DOKU_INC')) die('Meh.');
8 |
9 | /**
10 | * The page link type.
11 | */
12 | class plugin_strata_type_page extends plugin_strata_type {
13 | function __construct() {
14 | }
15 |
16 | function normalize($value, $hint) {
17 | global $ID;
18 |
19 | // fragment reference special case
20 | if(!empty($hint) && substr($hint,-1) == '#') {
21 | $value = $hint.$value;
22 | resolve_pageid(getNS($hint),$value,$exists);
23 | return $value;
24 | }
25 |
26 | $base = ($hint?:getNS($ID));
27 |
28 | // check for local link, and prefix full page id
29 | // (local links don't get resolved by resolve_pageid)
30 | if(preg_match('/^#.+/',$value)) {
31 | $value = $ID.$value;
32 | }
33 |
34 | // resolve page id with respect to selected base
35 | resolve_pageid($base,$value,$exists);
36 |
37 | // if the value is empty after resolving, it is a reference to the
38 | // root starting page. (We can not put the emtpy string into the
39 | // database as a normalized reference -- this will create problems)
40 | if($value == '') {
41 | global $conf;
42 | $value = $conf['start'];
43 | }
44 |
45 | return $value;
46 | }
47 |
48 | function render($mode, &$R, &$T, $value, $hint) {
49 | // render internal link
50 | // (':' is prepended to make sure we use an absolute pagename,
51 | // internallink resolves page names, but the name is already resolved.)
52 | $R->internallink(':'.$value);
53 | }
54 |
55 | function getInfo() {
56 | return array(
57 | 'desc'=>'Links to a wiki page. The optional hint is treated as namespace for the link. If the hint ends with a #, all values will be treated as fragments.',
58 | 'hint'=>'namespace'
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/types/ref.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 | // must be run within Dokuwiki
7 | if(!defined('DOKU_INC')) die('Meh.');
8 |
9 | /**
10 | * The reference link type.
11 | */
12 | class plugin_strata_type_ref extends plugin_strata_type_page {
13 | function __construct() {
14 | $this->util =& plugin_load('helper', 'strata_util');
15 | parent::__construct();
16 | }
17 |
18 | function render($mode, &$R, &$T, $value, $hint) {
19 | $heading = null;
20 |
21 | // only use heading if allowed by configuration
22 | if(useHeading('content')) {
23 | $titles = $T->fetchTriples($value, $this->util->getTitleKey());
24 | if($titles) {
25 | $heading = $titles[0]['object'];
26 | }
27 | }
28 |
29 | // render internal link
30 | // (':' is prepended to make sure we use an absolute pagename,
31 | // internallink resolves page names, but the name is already resolved.)
32 | $R->internallink(':'.$value, $heading);
33 | }
34 |
35 | function getInfo() {
36 | return array(
37 | 'desc'=>'References another piece of data or wiki page, and creates a link. The optional hint is used as namespace for the link. If the hint ends with a #, all values will be treated as fragments.',
38 | 'hint'=>'namespace'
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/types/text.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 | // must be run within Dokuwiki
7 | if(!defined('DOKU_INC')) die('Meh.');
8 |
9 | /**
10 | * The verbatim string type.
11 | */
12 | class plugin_strata_type_text extends plugin_strata_type {
13 | // uses base functionality
14 | function getInfo() {
15 | return array(
16 | 'desc'=>'Verbatim text. Does not format, ignores hint.'
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/types/wiki.php:
--------------------------------------------------------------------------------
1 |
5 | */
6 | // must be run within Dokuwiki
7 | if(!defined('DOKU_INC')) die('Meh.');
8 |
9 | use dokuwiki\Parsing\Parser;
10 | use dokuwiki\Parsing\Handler\Block;
11 |
12 |
13 | /**
14 | * The 'render as wiki text' type.
15 | */
16 | class plugin_strata_type_wiki extends plugin_strata_type {
17 | function normalize($value, $hint) {
18 | $ins = $this->_instructions($value);
19 |
20 | $value = "\n".str_replace("\r\n","\n",$value)."\n";
21 |
22 | for($i=count($ins)-1;$i>=0;$i--) {
23 | switch($ins[$i][0]) {
24 | case 'internallink':
25 | $replacement = $this->_normalize_internallink($ins[$i][1]);
26 | break;
27 | case 'locallink':
28 | $replacement = $this->_normalize_locallink($ins[$i][1]);
29 | break;
30 | case 'internalmedia':
31 | $replacement = $this->_normalize_media($ins[$i][1]);
32 | break;
33 | case 'externallink':
34 | $replacement = $this->_linkSyntax($ins[$i][1], $ins[$i][1][0]);
35 | break;
36 | default:
37 | continue 2;
38 | }
39 |
40 | $value = substr_replace($value, $replacement, $ins[$i][2], $ins[$i+1][2] - $ins[$i][2]);
41 | }
42 |
43 | // strip off only the inserted newlines
44 | return substr($value,1,-1);
45 | }
46 |
47 | /**
48 | * Normalizes an internal link.
49 | */
50 | function _normalize_internallink($instruction) {
51 | global $ID;
52 |
53 | // split off query string
54 | $parts = explode('?', $instruction[0] ,2);
55 |
56 | $id = $parts[0];
57 |
58 | list($id,$hash) = explode('#', $id, 2);
59 | // normalize selflink
60 | if($id === '') {
61 | $id = $ID;
62 | }
63 |
64 | // actually resolve the page
65 | resolve_pageid(getNS($ID), $id, $exists);
66 | // make the resolved pageid absolute, so it does not re-resolve to something else later on
67 | $id = ':'.$id;
68 |
69 | // render the link
70 | return $this->_linkSyntax($instruction, $id.'#'.$hash);
71 | }
72 |
73 | /**
74 | * Normalizes a local link.
75 | */
76 | function _normalize_locallink($instruction) {
77 | global $ID;
78 |
79 | // simply prefix the current page
80 | return $this->_linkSyntax($instruction, $ID.'#'.$instruction[0]);
81 | }
82 |
83 | /**
84 | * Normalizes a media array.
85 | */
86 | function _normalize_media($instruction) {
87 | global $ID;
88 |
89 | // construct media structure based on input
90 | if(isset($instruction['type'])) {
91 | $media = $instruction;
92 | } else {
93 | list($src, $title, $align, $width, $height, $cache, $linking) = $instruction;
94 | $media = compact('src','title','align','width','height');
95 | $media['type']= 'internalmedia';
96 | }
97 |
98 | // normalize internal media links
99 | if($media['type'] == 'internalmedia') {
100 | list($src,$hash) = explode('#',$media['src'],2);
101 | resolve_mediaid(getNS($ID),$src, $exists);
102 | if($hash) $src.='#'.$hash;
103 | $media['src'] = ':'.$src;
104 | }
105 |
106 | // render the media structure
107 | return $this->_mediaSyntax($media);
108 | }
109 |
110 | /**
111 | * Renders the media syntax.
112 | */
113 | function _mediaSyntax($media) {
114 | // the source
115 | $src = $media['src'];
116 |
117 | // the resizing part
118 | if(isset($media['width'])) {
119 | $size = '?'.$media['width'];
120 | if(isset($media['height'])) {
121 | $size .= 'x'.$media['height'];
122 | }
123 | } else {
124 | $size = '';
125 | }
126 |
127 | // the title part
128 | if(isset($media['title'])) {
129 | $title = '|'.$media['title'];
130 | } else {
131 | $title = '';
132 | }
133 |
134 | // the alignment parts
135 | if(isset($media['align'])) {
136 | switch($media['align']) {
137 | case 'left':
138 | $al = ''; $ar = ' '; break;
139 | case 'right':
140 | $al = ' '; $ar = ''; break;
141 | case 'center':
142 | $al = ' '; $ar = ' '; break;
143 | }
144 | }
145 |
146 | // construct the syntax
147 | return '{{'.$al.$src.$size.$ar.$title.'}}';
148 | }
149 |
150 | /**
151 | * Renders the link syntax, invoking media normalization
152 | * if required.
153 | */
154 | function _linkSyntax($instruction, $newLink) {
155 | // fetch params from old link
156 | $parts = explode('?', $instruction[0],2);
157 | if(count($parts) === 2) {
158 | $params = '?'.$parts[1];
159 | } else {
160 | $params = '';
161 | }
162 |
163 | // determine title
164 | $title = '';
165 | if(isset($instruction[1])) {
166 | if(is_array($instruction[1])) {
167 | if($instruction[1]['type'] == 'internalmedia') {
168 | $title='|'.$this->_normalize_media($instruction[1]);
169 | } else {
170 | $title='|'.$this->_mediaSyntax($instruction[1]);
171 | }
172 | } else {
173 | $title = '|'.$instruction[1];
174 | }
175 | }
176 |
177 | // construct a new link string
178 | return '[['.$newLink.$params.$title.']]';
179 |
180 | }
181 |
182 | function render($mode, &$R, &$T, $value, $hint) {
183 | // though this breaks backlink functionality, we really do not want
184 | // metadata renders of included pieces of wiki.
185 | if($mode == 'xhtml' || $mode == 'odt') {
186 | $instructions = $this->_instructions($value);
187 | $instructions = array_slice($instructions, 2, -2);
188 |
189 | // last-minute fix of newline in front of content
190 | if(!empty($instructions[0][0]) && $instructions[0][0]=='cdata') {
191 | $instructions[0][1][0] = ltrim($instructions[0][1][0]);
192 | }
193 |
194 | // actual render of content
195 | $R->nest($instructions);
196 | }
197 | }
198 |
199 | function getInfo() {
200 | return array(
201 | 'desc'=>'Allows the use of dokuwiki syntax; only non-block syntax is allowed (only links, formatting, etc.; no tables, headers, and other large stuff). The hint is ignored.',
202 | 'tags'=>array()
203 | );
204 | }
205 |
206 | function _instructions($text) {
207 | // determine all parser modes that are allowable as inline modes
208 | // (i.e., those that are allowed inside a table cell, minus those
209 | // that have a paragraph type other than 'normal')
210 |
211 | // determine all modes allowed inside a table cell or list item
212 |
213 | // import parser classes and mode definitions to make the $PARSER_MODES global available to us
214 | require_once(DOKU_INC . 'inc/parser/parser.php');
215 | global $PARSER_MODES;
216 | $allowedModes = array_merge(
217 | $PARSER_MODES['formatting'],
218 | $PARSER_MODES['substition'],
219 | $PARSER_MODES['disabled'],
220 | $PARSER_MODES['protected']
221 | );
222 |
223 | // determine all modes that are not allowed either due to paragraph
224 | // handling, or because they're blacklisted as they don't make sense.
225 | $blockHandler = new Block();
226 | $disallowedModes = array_merge(
227 | // inlined from Block::blockOpen due to being protected:
228 | array(
229 | 'header',
230 | 'listu_open','listo_open','listitem_open','listcontent_open',
231 | 'table_open','tablerow_open','tablecell_open','tableheader_open','tablethead_open',
232 | 'quote_open',
233 | 'code','file','hr','preformatted','rss',
234 | 'htmlblock','phpblock',
235 | 'footnote_open',
236 | ),
237 | // inlined from Block::stackOpen due to being protected:
238 | array(
239 | 'section_open',
240 | ),
241 | array('notoc', 'nocache')
242 | );
243 |
244 | $allowedModes = array_diff($allowedModes, $disallowedModes);
245 |
246 | $parser = new Parser(new Doku_Handler());
247 |
248 | foreach(p_get_parsermodes() as $mode) {
249 | if(!in_array($mode['mode'], $allowedModes)) continue;
250 | $parser->addMode($mode['mode'], $mode['obj']);
251 | }
252 |
253 | trigger_event('PARSER_WIKITEXT_PREPROCESS', $text);
254 | $p = $parser->parse($text);
255 | return $p;
256 | }
257 | }
258 |
--------------------------------------------------------------------------------