mkws-manual.html
mkws-manual.odt
mkws-manual.pdf
+mkws-developer.html
+mkws-developer.odt
+mkws-developer.pdf
# Copyright (c) 2013-2014 IndexData ApS. http://indexdata.com
DOCS = README.html README.odt README.pdf \
- mkws-manual.html mkws-manual.odt mkws-manual.pdf
+ mkws-manual.html mkws-manual.odt mkws-manual.pdf \
+ mkws-developer.html mkws-developer.odt mkws-developer.pdf
-INSTALLABLE = README.html mkws-manual.html mkws-doc.css
+INSTALLABLE = README.html mkws-manual.html mkws-developer.html mkws-doc.css
INSTALLED = $(INSTALLABLE:%=../tools/htdocs/%)
install: $(INSTALLED)
--- /dev/null
+% The MasterKey Widget Set developer's guide
+% Mike Taylor
+% 11 August 2014
+
+
+Overview
+========
+
+Core concepts
+-------------
+
+Development with MKWS consists primarily of defining new types of
+widgets. These can interact with the core functionality is several
+defined ways.
+
+You create a new widget type by calling the `mkws.registerWidgetType`
+function, passing in the widget name and a function. The name is used
+to recognise HTML elements as being widgets of this type -- for
+example, if you register a `Foo` widget, elements like
+`<div class="mkwsFoo">` will be widgets of this type.
+
+The function promotes a bare widget object (passed as `this`) into a
+widget of the appropriate type. MKWS doesn't use classes or explicit
+prototypes: it just makes objects that have the necessary
+behaviours. There are _no_ behaviours that Widgets are obliged to
+provide: you can make a doesn't-do-anything-at-all widget if you like:
+
+ mkws.registerWidgetType('Sluggard', function() {});
+
+More commonly, widgets will subscribe to one or more events, so that
+they're notified when something interesting happens. For example, the
+`Log` widget asks to be notified when a `log` event happens, and
+appends the logged message to its node, as follows:
+
+ mkws.registerWidgetType('Log', function() {
+ var that = this;
+
+ this.team.queue("log").subscribe(function(teamName, timestamp, message) {
+ $(that.node).append(teamName + ": " + timestamp + message + "<br/>");
+ });
+ });
+
+This simple widget illustrates several important points:
+
+* The base widget object (`this`) has several baked-in properties and
+ methods that are available to individual widgets. These include
+ `this.team` (the team that this widget is a part of) and `this.node`
+ (the DOM element of the widget). See below for a full list.
+
+* The team object (`this.team`) also has baked-in properties and
+ methods. These include the `queue` function, which takes an event-name
+ as its argument. See below for a full list.
+
+* You can add functionality to a widget by subscribing it to an
+ event's queue using `this.team.queue("EVENT").subscribe`. The
+ argument is a function which is called whenever the event is
+ published. The arguments to the function are different for different
+ events.
+
+* As with so much JavaScript programming, the value of the special
+ variable `this` is lost inside the `subscribez` callback function,
+ so it must be saved if it's to be used inside that callback
+ (typically as a local variable named `that`).
+
+
+Widget specialisation (inheritance)
+-----------------------------------
+
+Many widgets are simple specialisations of existing widgets. For
+example, the `Record` widget is the same as the `Records` widget
+except that it defaults to displaying a single record. It's defined as
+follows:
+
+ mkws.registerWidgetType('Record', function() {
+ mkws.promotionFunction('Records').call(this);
+ if (!this.config.maxrecs) this.config.maxrecs = 1;
+ });
+
+Remember that when a promotion function is called, it's passed a base
+widget object that's not specialised for any particular task. To make
+a specialised widget, you first promote that base widget into the type
+that you want to specialise from -- in this case, `Records` -- using
+the promotion function that's been registered for that type.
+
+Once this has been done, the specialisations can be introduced. In
+this case, it's a very simple matter of changing the `maxrecs`
+configuration setting to 1 unless it's already been given an explicit
+value. (That would occur if the HTML used an element like `<div
+class="mkwsRecord" maxrecs="2">`, though it's not obvious why anyone
+would do that.)
+
+
+Reference Guide
+===============
+
+
+Widget properties and methods
+-----------------------------
+
+The following properties and methods exist in the bare widget object
+that is passed into `registerWidgetType`'s callback function, and can
+be used by the derived widget.
+
+* `String this.type` --
+ A string containing the type of the widget.
+
+* `Team this.team` --
+ The team object to which this widget belongs. The team has
+ several additional important properties and methods, described
+ below.
+
+* `DOMElement this.node` --
+ The DOM element of the widget
+
+* `Hash this.config` --
+ A table of configuration values for the widget. This table
+ inherits missing values from the team's configuration, which
+ in turn inherits from the top-level MKWS configuration, which
+ inherits from the default configuration. Instances of widgets
+ in HTML can set configuration items as HTML attributes: for
+ example, the HTML element
+ `<div class="mkwsRecords" maxrecs="10">`.
+ creates a widget for which `this.config.maxrecs` is set to 10.
+
+* `String this.toString()` --
+ A function returning a string that briefly names this
+ widget. Can be useful in logging.
+
+* `Void this.log(string)` --
+ A function to log a string for debugging purposes. The string
+ is written on the browser console, and also published to any
+ subcribers to the `log` event.
+
+* `String this.value()` --
+ A function returning the value of the widget's HTML element.
+
+* `VOID autosearch()` --
+ Registers that this kind of widget is one that requires an
+ automatic search to be run for it if an `autosearch` attribute
+ is provided on the HTML element. This is appropriate for
+ widgets such as `Records` and `Facet` that display some part
+ of a search result.
+
+* `VOID hideWhenNarrow()` --
+ Registers that this widget should hide itself when the page
+ becomes "narrow" -- that is, fewer pixels in width that the
+ threshhold value specified by the top-level configuration item
+ `responsive_design_width`. Should be used for "unimportant"
+ widgets that can be omitted from the mobile version of a site.
+
+* `expandValue()` --
+ TODO: either document this or remove it from the API.
+
+* `subwidget(type, overrides, defaults)` --
+ Returns the HTML of a subwidget of the specified type, which
+ can then be inserted into the widget using the
+ `this.node.html` function. The subwidget is given the same
+ attributes at the parent widget that invokes this function,
+ except where overrides are passed in. If defaults are also
+ provided, then these are used when the parent widget provides
+ no values. Both the `overrides` and `defaults` arguments are
+ hashes: the latter is optional.
+
+ See for example the `Credo` widget defined in the example
+ area's `mkws-widget-credo.js` file. This uses several
+ invocations of `subwidget` to create a complex compound widget
+ with numerous text, facet and image panes. TODO: rename this
+ widget and everything related to it.
+
+In addition to these properties and methods of the bare widget object,
+some kinds of specific widget add other properties of their own. For
+example, the `Builder` widget uses a `callback` property as the
+function that it use to publish the widget definition that it
+constructs. This defaults to the builtin function `alert`, but can be
+overridden by derived widgets such as `ConsoleBuilder`.
+
+
+Team methods
+------------
+
+Since the team object is supposed to be opaque to widgets, all access
+is via the following API methods rather than direct access to
+properties.
+
+* `String team.name()`
+* `Bool team.submitted()`
+* `Num team.perpage()`
+* `Num team.totalRecordCount()`
+* `Num team.currentPage();`
+* `String team.currentRecordId()`
+* `String team.currentRecordData()`
+
+These are all simple accessor functions that provide the ability to
+read properties of the team.
+
+* `Array team.filters()` --
+ Another accessor function, providing access to the array of
+ prevailing filters (which narrow the search results by means
+ of Pazpar2 filters and limits). This is really too complicated
+ an object for the widgets to be given access to, but it's
+ convenient to do it this way. If you must insist on using
+ this, see the `Navi` widget, which is the only place it's used.
+
+* `Bool team.targetFiltered(targetId)` --
+ Indicates whether the specified target has been filtered by
+ selection as a facet. This is used only by the `Facet` widget,
+ and there is probably no reason for you to use it.
+
+* `Hash team.config()` --
+ Access to the team's configuration settings. There is almost
+ certainly no reason to use this: the settings that haven't
+ been overridden are accessible via `this.config`.
+
+* `Void team.set_sortOrder(string)`, `Void team.set_perpage(number)` --
+ "Setter" functions for the team's sortOrder and perpage
+ functions. Unlikely to be needed outside of the `Sort` and
+ `Perpage` widgets.
+
+* `Queue team.queue(eventName)` --
+ Returns the queue associated with the named event: this can be
+ used to subscribe to the event (or more rarely to publish it).
+
+* `Void team.newSearch(query, sortOrder, maxrecs, perpage, limit, targets, targetfilter)` --
+ Starts a new search with the specified parameters. All but the
+ query may be omitted, in which case the prevailing defaults
+ are used.
+
+* `Void team.reShow()` --
+ Using the existing search, re-shows the result records after a
+ change in sort-order, per-page count, etc.
+
+* `String team.recordElementId(recordId)` --
+ Utility function for converting a record identifer (returned
+ from Pazpar2) into a version suitable for use as an HTML
+ element ID.
+
+* `String team.renderDetails(recordData)` --
+ Utility function returns an HTML rendering of the record
+ represented by the specified data.
+
+* `Template team.loadTemplate(templateName)` --
+ Loads (or retrieves from cache) the named Handlebars template,
+ and returns it in a form that can be invoked as a function,
+ passed a data-set.
+
+Some of these methods either (A) are really too low-level and should
+not be exposed, or (B) should be widget-level methods. The present
+infelicities reflect the fact that some code that rightly belongs in
+widgets is still in the team. When we finish migrating it, the widget
+API should get simpler.
+
+
+Events
+------
+
+TODO: list of events that can be usefully subscribed to.
+
+
+- - -
+
+Copyright (C) 2013-2014 by IndexData ApS, <http://www.indexdata.com>
+++ /dev/null
-INTRODUCTION
-============
-
-Development with MKWS consists primarily of defining new types of
-widgets. These can interact with the core functionality is several
-defined ways.
-
-You create a new widget type by calling the mkws.registerWidgetType
-function, passing in the widget name and a function. The name is used
-to recognise HTML elements as being widgets of this type -- for
-example, if you register a "Foo" widget, elements like <div
-class="mkwsFoo"> will be widgets of this type.
-
-The function promotes a bare widget object (passed as `this') into a
-widget of the appropriate type. MKWS doesn't use classes or explicit
-prototypes: it just makes objects that have the necessary
-behaviours. Widgets have *no* behaviours that they have to provide:
-you can make a doesn't-do-anything-at-all widget if you like:
-
- mkws.registerWidgetType('Sluggard', function() {});
-
-More commonly, widgets will subscribe to one or more events, so that
-they're notified when something interesting happens. For example, the
-"Log" widget asks to be notified when a "log" event happens, and
-appends the logged message to its node, as follows:
-
- mkws.registerWidgetType('Log', function() {
- var that = this;
-
- this.team.queue("log").subscribe(function(teamName, timestamp, message) {
- $(that.node).append(teamName + ": " + timestamp + message + "<br/>");
- });
- });
-
-This simple widget illustrates several important points:
-
-* The base widget object (`this') has several baked-in properties and
- methods that are available to individual widgets. These include
- this.team (the team that this widget is a part of) and this.node
- (the DOM element of the widget).
-
-* The team object (`this.team') also has baked-in properties and
- methods. These include the queue function, which takes an event-name
- as its argument. It's possible to subscribe to an event's queue
- using this.team.queue("EVENT").subscribe. The argument is a function
- which is called whenever the event is published. The arguments to
- the function are different for different events.
-
-* The value of `this' is lost inside the subscribe callback, so it
- must be saved if it's to be used inside that callback (typically as
- a local variable named `that').
-
-
-SPECIALISATION (INHERITANCE)
-============================
-
-Many widgets are simple specialisations of existing widgets. For
-example, the "Record" widget is the same as the "Records" widget
-except that it defaults to displaying a single record. It's defined as
-follows:
-
- mkws.registerWidgetType('Record', function() {
- mkws.promotionFunction('Records').call(this);
- if (!this.config.maxrecs) this.config.maxrecs = 1;
- });
-
-Remember that when a promotion function is called, it's passed a base
-widget object that's not specialised for any particular task. To make
-a specialised widget, first promote that base widget into the type
-that you want to specialise from -- in this case, "Records" -- using
-the promotion function that's been registered for that type.
-
-Once this has been done, the specialisations can be introduced. In
-this case, it's a very simple matter of changing the "maxrecs"
-configuration setting to 1 unless it's already been given an explicit
-value. (That would occur if the HTML used an element like <div
-class="mkwsRecord" maxrecs="2">, though it's not obvious why anyone
-would do that.)
-
-
-WIDGET PROPERTIES AND METHODS
-=============================
-
-String this.type
- A string containing the type of the widget.
-
-Team this.team
- The team object to which this widget belongs. The team has
- several additional important properties and methods, described
- below.
-
-DOMElement this.node
- The DOM element of the widget
-
-Hash this.config
- A table of configuration values for the widget. This table
- inherits missing values from the team's configuration, which
- in turn inherits from the top-level MKWS configuration, which
- inherits from the default configuration. Instances of widgets
- in HTML can set configuration items as HTML attributes, as in
- <div class="mkwsRecords" maxrecs="2">.
-
-String this.toString()
- A function returning a string that briefly names this
- widget. Can be useful in logging.
-
-Void this.log(string)
- A function to log a string for debugging purposes. The string
- is written on the browser console, and also published to any
- "log" subcribers.
-
-String this.value()
- A function returning the value of the widget's HTML element.
-
-
-TEAM METHODS
-============
-
-Since the team object is supposed to be opaque to widgets, all access
-is via the following API methods rather than direct access to
-properties.
-
-String team.name()
-Bool team.submitted()
-Num team.perpage()
-Num team.totalRecordCount()
-Num team.currentPage();
-String team.currentRecordId()
-String team.currentRecordData()
- Simple accessor functions that provide the ability to read
- properties of the team.
-
-Array team.filters()
- Another accessor function, providing access to the array of
- prevailing filters (which narrow the search results by means
- of Pazpar2 filters and limits). This is really too complicated
- an object for the widgets to be given access to, but it's
- convenient to do it this way. See the "Navi" widget, which is
- the only place it's used.
-
-Hash team.config()
- Access to the team's configuration settings. There is almost
- certainly no reason to use this: the settings that haven't
- been overridden are accessible via this.config.
-
-Void team.set_sortOrder(string)
-Void team.set_perpage(number)
- "Setter" functions for the team's sortOrder and perpage
- functions. Unlikely to be needed outside of the "Sort" and
- "Perpage" widgets.
-
-Queue team.queue(eventName)
- Returns the queue associated with the named event: this can be
- used to subscribe to the event (or more rarely to publish it).
-
-Bool team.targetFiltered(targetId)
- Indicates whether the specified target has been filtered by
- selection as a facet.
-
-Void team.newSearch(query, sortOrder, maxrecs, perpage, limit, targets, targetfilter)
- Starts a new search with the specified parameters. All but the
- query may be omitted, in which case the prevailing defaults
- are used.
-
-Void team.reShow()
- Using the existing search, re-shows the result records after a
- change in sort-order, per-page count, etc.
-
-String team.recordElementId(recordId)
- Utility function for converting a record identifer (returned
- from Pazpar2) into a version suitable for use as an HTML
- element ID.
-
-String team.renderDetails(recordData)
- Utility function returns an HTML rendering of the record
- represented by the specified data.
-
-Template team.loadTemplate(templateName)
- Loads (or retrieves from cache) the named Handlebars template,
- and returns it in a form that can be invoked as a function,
- passed a data-set.
-
-Some of these methods either (A) are really too low-level and should
-not be exposed, or (B) should be widget-level methods. The present
-infelicities reflect the fact that some code that rightly belongs in
-widgets is still in the team. When we finish migrating it, the widget
-API should get simpler.
-
table tr:nth-child(even) {
background: #bfdcf8;
}
-
-/*
- * Works with the HTML emitted by pandoc. It would better if pandoc
- * were to emit class names that we can use. But it doesn't.
- */
-body > p:last-of-type {
- font-size: small;
- max-width: none;
- text-align: right;
-}
</div>
----
-Element Type Default Description
--------- ----- --------- ------------
-popup_width string 880 Width of the popup window (if used), in
- pixels.
+Element Type Default Description
+-------- ----- ------- ------------
+popup_width string 880 Width of the popup window (if used), in
+ pixels.
-popup_height string 760 Height of the popup window (if used), in
- pixels.
+popup_height string 760 Height of the popup window (if used), in
+ pixels.
-popup_button string input.mkwsButton (Never change this.)
+popup_button string `input.mkwsButton` (Never change this.)
-popup_modal string 0 Modal confirmation mode. Valid values are 0 or 1
+popup_modal string 0 Modal confirmation mode. Valid values are 0 or 1
-popup_autoOpen string 1 Open popup window on load. Valid values are 0 or 1
+popup_autoOpen string 1 Open popup window on load. Valid values are 0 or 1
----
Alias /jasmine/ /usr/local/src/git/mkws/examples/jasmine/
Alias /test/ /usr/local/src/git/mkws/test/
+ <Directory />
+ Allow from all
+ </Directory>
+
ErrorLog /var/log/apache2/mkws-examples-error.log
CustomLog /var/log/apache2/mkws-examples-access.log combined
<link rel="stylesheet" type="text/css" href="tools/htdocs/mkws.css" />
<script type="text/javascript">
var mkws_config = {
- pazpar2_url: "//sp-mkws.indexdata.com/service-proxy/",
// For now, we have to provide known-bad credentials to skip user/pw login: see bug MKSP-125
- service_proxy_auth: "//sp-mkws.indexdata.com/service-proxy/?command=auth&action=perconfig&username=XXX&password=XXX"
- // For explicit credential-based authentication, add: &username=orex&password=orexmkc
+ // Was: //sp-mkws.indexdata.com/service-proxy/?command=auth&action=perconfig&username=XXX&password=XXX
+ pp2_hostname: "x.sp-mkws.indexdata.com",
+ sp_path: "service-proxy/",
+ sp_auth_query: "command=auth&action=perconfig",
+ sp_auth_credentials: "XXX/XXX",
+ // ### This should automatically follow pp2_hostname
+ pazpar2_url: "//x.sp-mkws.indexdata.com/service-proxy/",
};
</script>
- <script type="text/javascript" src="//code.jquery.com/jquery-1.10.0.min.js"></script>
+ <script type="text/javascript" src="tools/htdocs/jquery-1.10.0.min.js"></script>
<script type="text/javascript" src="tools/htdocs/jquery.json-2.4.js"></script>
<script type="text/javascript" src="tools/htdocs/handlebars-v1.1.2.js"></script>
<script type="text/javascript" src="tools/htdocs/pz2.js"></script>
var config_default = {
use_service_proxy: true,
pazpar2_url: "//mkws.indexdata.com/service-proxy/",
- service_proxy_auth: "//mkws.indexdata.com/service-proxy-auth",
+ service_proxy_auth: undefined, // generally rolled from the next three properties
+ // Was: //mkws.indexdata.com/service-proxy-auth
+ pp2_hostname: "mkws.indexdata.com",
+ sp_path: "service-proxy-auth",
+ sp_auth_query: undefined, // Will be: "command=auth&action=perconfig",
+ sp_auth_credentials: undefined,
lang: "",
sort_options: [["relevance"], ["title:1", "title"], ["date:0", "newest"], ["date:1", "oldest"]],
perpage_options: [10, 20, 30, 50],
}
*/
+ function sp_auth_url(config) {
+ if (config.service_proxy_auth) {
+ mkws.log("using pre-baked sp_auth_url '" + config.service_proxy_auth + "'");
+ return config.service_proxy_auth;
+ } else {
+ var s = '//';
+ s += config.auth_hostname ? config.auth_hostname : config.pp2_hostname;
+ s += '/' + config.sp_path;
+ var q = config.sp_auth_query;
+ if (q) {
+ s += '?' + q;
+ }
+ var c = config.sp_auth_credentials;
+ if (c) {
+ s += ('&username=' + c.substr(0, c.indexOf('/')) +
+ '&password=' + c.substr(c.indexOf('/')+1));
+ }
+ mkws.log("generated sp_auth_url '" + s + "'");
+ return s;
+ }
+ }
+
if (mkws.config.use_service_proxy && !mkws.authenticated && !mkws.authenticating) {
- authenticateSession(mkws.config.service_proxy_auth,
+ authenticateSession(sp_auth_url(mkws.config),
mkws.config.service_proxy_auth_domain,
mkws.config.pazpar2_url);
} else if (!mkws.authenticating) {
var that = this;
var team = this.team;
- this.button = mkws.$('<button/>', {
+ var button = mkws.$('<button/>', {
type: 'button',
text: this.config.text || "Build!"
});
- this.node.append(this.button);
- this.button.click(function() {
+ this.node.append(button);
+ button.click(function() {
var query = team.widget('Query').value();
var sort = team.widget('Sort').value();
var perpage = team.widget('Perpage').value();
return s.join('');
};
+ // ### why is this a member function? It's never called from outside this file.
that.expandValue = function(val) {
if (val.match(/^!param!/)) {
var param = val.replace(/^!param!/, '');
--- /dev/null
+<VirtualHost *:80>
+ ServerName x.sp-mkws.indexdata.com
+
+ ErrorLog /var/log/apache2/sp-mkws-error.log
+ CustomLog /var/log/apache2/sp-mkws-access.log combined
+
+ DocumentRoot /usr/local/src/git/mkws/tools/sp-htdocs
+
+ Header set Access-Control-Allow-Credentials true
+
+ ProxyPreserveHost On
+ # We could use any of the following:
+ ProxyPass /service-proxy/ http://x.sp-mkws.indexdata.com:8585/service-proxy/
+ ProxyPassReverse /service-proxy/ http://x.sp-mkws.indexdata.com:8585/service-proxy/
+
+# PerlOptions +Parent
+# PerlSwitches -I/home/indexdata/mkws/tools/mod_perl
+# <Location /service-proxy>
+# PerlOutputFilterHandler MyApache2::SetACAO
+# </Location>
+</VirtualHost>
mkws.min.js
pz2.js
mkws-manual.html
+mkws-developer.html
--- /dev/null
+service-proxy-mike-mac.properties
--- /dev/null
+MIKE = service-proxy-mike-mac.properties
+
+$(MIKE): service-proxy.properties Makefile
+ rm -f $@
+ sed 's/mkc-admin.indexdata.com/x.&:8181/g' $< > $@
+ chmod 444 $@
+
+clean:
+ rm -f $(MIKE)
+
--- /dev/null
+This contains an ICU-stripped version of the coniguration used by the
+live MKC service, since I (Mike) need to run it on a Mac where YAZ is
+installed (using "brew install yaz") compiled without ICU.
+
+NOTE TO SELF:
+shell1$ cd /usr/local/src/git/service-proxy/sp-runtime; mvn jetty:run-war
+shell2$ cd /usr/local/src/git/torus; mvn jetty:run-war
+shell3$ cd /usr/local/src/git/mkws/tools/service-proxy/pazpar2 && /usr/local/sbin/pazpar2 -f server.no-icu.xml
+shell4$ cd /usr/local/src/git/idzebra/examples/gils && zebrasrv tcp:@:9998
+shell5$ yaz-ztest
+
+THEN USE:
+http://x.mkc-admin.indexdata.com/console/
+http://x.example.indexdata.com/mike.html
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<pazpar2 xmlns="http://www.indexdata.com/pazpar2/1.0">
+ <threads number="0"/> <!-- non-zero to enable threaded operation -->
+ <file path=".:/usr/share/pazpar2/xsl"/>
+ <server>
+ <listen port="8004"/>
+ <include src="service.no-icu.xml"/>
+ </server>
+</pazpar2>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This is NOT used by the live system, which shares MKC's Pazpar2 config.
+ It's only for Mike to run on his development system, which lacks ICU -->
+<service id="mkc" xmlns="http://www.indexdata.com/pazpar2/1.0">
+ <timeout session="60" z3950_operation="30" z3950_session="180"/>
+
+ <rank cluster="yes"
+ follow="0.0"
+ lead="1.0"
+ length="linear"
+ debug="no" />
+
+ <settings src="settings-testserver.xml"/>
+
+ <!-- we try to keep same order as in marc21.xsl -->
+ <metadata name="id" brief="yes"/>
+ <metadata name="lccn" merge="unique"/>
+ <metadata name="isbn" brief="yes" merge="unique"/>
+ <metadata name="issn" brief="yes" merge="unique"/>
+ <metadata name="tech-rep-nr"/>
+ <metadata name="author" brief="yes" termlist="yes" merge="unique"
+ rank="2" mergekey="optional" limitmap="ccl: au" />
+ <metadata name="author-title"/>
+ <metadata name="author-date"/>
+ <metadata name="corporate-name"/>
+ <metadata name="corporate-location"/>
+ <metadata name="corporate-date"/>
+ <metadata name="meeting-name"/>
+ <metadata name="meeting-location"/>
+ <metadata name="meeting-date"/>
+ <metadata name="date" brief="yes" sortkey="numeric" type="year"
+ merge="range" termlist="yes" limitmap="ccl: date"/>
+ <metadata name="title" brief="yes" sortkey="skiparticle"
+ merge="longest" rank="6" mergekey="required"/>
+ <metadata name="title-remainder" brief="yes" merge="longest" rank="5"/>
+ <metadata name="title-responsibility" brief="yes" merge="longest"/>
+ <metadata name="title-dates" brief="yes" merge="longest"/>
+ <metadata name="title-medium" brief="yes" merge="unique"
+ termlist="yes" sortkey="skiparticle" limitmap="local:"/>
+ <metadata name="title-number-section" brief="yes" merge="longest"/>
+ <metadata name="title-complete"/>
+ <metadata name="title-uniform"/>
+ <metadata name="medium" brief="yes" merge="unique" limitmap="local:" />
+ <metadata name="iii-id" brief="yes" merge="no"/>
+ <metadata name="edition"/>
+ <metadata name="publication-place"/>
+ <metadata name="publication-name"/>
+ <metadata name="publication-date"/>
+ <metadata name="physical-extent"/>
+ <metadata name="physical-format"/>
+ <metadata name="physical-dimensions"/>
+ <metadata name="physical-accomp"/>
+ <metadata name="physical-unittype"/>
+ <metadata name="physical-unitsize"/>
+ <metadata name="physical-specified"/>
+
+ <metadata name="series-title" brief="yes" merge="unique"/>
+
+ <metadata name="description" brief="yes" merge="unique" rank="3"/>
+ <metadata name="subject-long" rank="3"/>
+ <metadata name="subject" termlist="yes" rank="0" limitmap="ccl: su"
+ brief="yes" merge="unique"/>
+ <metadata name="snippet" brief="yes" merge="unique"/>
+ <metadata name="electronic-url" brief="yes" merge="unique"/>
+ <metadata name="electronic-format-type" />
+ <metadata name="electronic-format-instruction" />
+ <metadata name="electronic-text" brief="yes" merge="no"/>
+ <metadata name="electronic-note"/>
+ <metadata name="citation"/>
+ <metadata name="holding"/>
+ <metadata name="fulltext"/>
+ <metadata name="has-fulltext"/>
+ <metadata name="oclc-number"/>
+ <metadata name="system-control-nr"/>
+ <metadata name="locallocation" brief="yes"/>
+ <metadata name="callnumber" brief="yes"/>
+ <metadata name="publicnote" brief="yes"/>
+
+ <!-- journals -->
+ <metadata name="journal-title"/>
+ <metadata name="journal-subpart"/>
+ <metadata name="volume-number"/>
+ <metadata name="issue-date"/>
+ <metadata name="issue-number"/>
+ <metadata name="pages-number"/>
+
+ <metadata name="url_recipe" setting="postproc" brief="yes" merge="no"/>
+ <metadata name="open_url_resolver" setting="parameter" merge="no"/>
+ <metadata name="open-url" merge="longest"/>
+ <metadata name="use_url_proxy" setting="postproc" brief="yes" merge="no"/>
+
+ <metadata name="publisher"/>
+ <metadata name="available" brief="yes" merge="no" limitmap="local:"/>
+ <metadata name="due"/>
+ <metadata name="thumburl" brief="yes" merge="unique"/>
+ <metadata name="score"/>
+
+ <!-- normarc fields -->
+
+ <metadata name="price" merge="no"/>
+ <metadata name="locallocation-section" brief="yes" merge="no"
+ termlist="yes" limitmap="local:"/>
+ <metadata name="circulation-status" brief="yes" merge="no"
+ termlist="yes" limitmap="local:"/>
+ <metadata name="circulation-restriction" brief="yes" merge="no"/>
+ <metadata name="bibliofil-id" brief="yes" merge="no"/>
+ <metadata name="bibliofil-url" brief="yes" merge="no"/>
+ <metadata name="audience" brief="yes" merge="unique" termlist="yes"
+ limitmap="local:"/>
+
+</service>
+<!-- Keep this comment at the end of the file
+ Local variables:
+ mode: nxml
+ End:
+oooo
+-->
+
--- /dev/null
+<settings target="localhost:9999/Default">
+
+ <!-- Simple test against a local test server (could be yaz-ztest) -->
+
+ <set name="pz:name" value="Local Test"/>
+ <set name="pz:apdulog" value="1"/>
+
+ <!-- mapping for unqualified search -->
+ <set name="pz:cclmap:term" value="u=1016 t=l,r s=al"/>
+
+ <!-- field-specific mappings -->
+ <set name="pz:cclmap:ti" value="u=4 s=al"/>
+ <set name="pz:cclmap:su" value="u=21 s=al"/>
+ <set name="pz:cclmap:isbn" value="u=7"/>
+ <set name="pz:cclmap:issn" value="u=8"/>
+ <set name="pz:cclmap:date" value="u=30 r=r"/>
+
+ <!-- Retrieval settings -->
+
+ <set name="pz:requestsyntax" value="marc21"/>
+ <set name="pz:elements" value="F"/>
+
+ <!-- Result normalization settings -->
+
+ <set name="pz:nativesyntax" value="iso2709"/>
+ <set name="pz:xslt" value="marc21.xsl"/>
+
+</settings>
--- /dev/null
+../../../../pazpar2/etc/xsl/tmarc.xsl
\ No newline at end of file