Projects/worblehat-old
Projects
/
worblehat-old
Archived
12
0
Fork 0

Compare commits

..

No commits in common. "e1e280b31fddd9ac5248f36d4af5bd46e9007232" and "09e2bc414d11c26cb75cc27aae87b9f5f8f2e3fc" have entirely different histories.

13 changed files with 67 additions and 278 deletions

View File

@ -1,35 +0,0 @@
![](./wiki/graphics/project_icon.png)
# Worblehat
Worblehat er PVVs biblioteksystem.
- [FAQ](./wiki/faq.md)
## Foreløpige planer
### Database
Vi skal ha en PostgreSQL-database, med designet gitt i [`db.txt`](./db.txt). Er designet feil, vennligst rett.
### Informasjonsinnhenting
Tenkte å bruke protokollen Z39.50 mot BIBSYS. Vi skal ha et kommandolinjegrensesnitt for å legge inn bøker. Vi skal kjøpe en strekkodeleser for å blippe inn ISBN-nummeret, og systemet skal slå opp dette i BIBSYS. Resultatene skal returneres, og vi skal velge riktig bok, som så skal bli lagt inn i databasen. Dette bør det være mulig å gjøre både brukervennlig for registrering av en bok, og raskt ved registrering av flere hundre bøker.
### Utspørring
Kommandolinjegrensesnitt, IRC-bot og/eller web-grensesnitt for å spørre databasen. IRC-boten skal ha et logikklag mellom seg selv og databasen, for å slippe så mye logikk internt. Dette laget kan også de andre grensesnittene bruke.
### Ressurser
#### Informasjon om bøkene
**Z39.50**
- https://www.bibsys.no/wps/wcm/connect/BIBSYS+Eng/Main+Menu/Search/Z39.50+Service+Description
- https://www.norzig.no/
**Klassifikasjon**
- https://en.wikipedia.org/wiki/Universal_Decimal_Classification
- https://en.wikipedia.org/wiki/Dewey_Decimal_Classification

View File

@ -1,3 +0,0 @@
class WorblehatException(Exception):
def __init__(self, msg):
Exception.__init__(self, msg)

View File

@ -1,79 +0,0 @@
import os
import tempfile
import locale
import sys
from exc import WorblehatException
stdout_encoding = locale.getpreferredencoding()
file_encoding = 'utf-8'
output = sys.stdout
# def write_tmpfile(pfix, content, encoding='utf8'):
# file = tempfile.NamedTemporaryFile(prefix=pfix+'-', dir='/tmp', delete=False)
# file.write(content.encode(encoding))
# name = file.name
# file.close()
# return name
def open_tmpfile(prefix):
global output
if output != sys.stdout:
raise WorblehatException('open_tmpfile: Already writing to a file')
tmpfile = tempfile.NamedTemporaryFile(prefix='worblehat-%s-' % prefix,
dir='/tmp',
delete=False)
output = tmpfile
return tmpfile.name
def close_tmpfile():
global output
if output == sys.stdout:
raise WorblehatException('close_tmpfile: No file open')
output.close()
output = sys.stdout
def output_encoding():
if output == sys.stdout:
return stdout_encoding
else:
return file_encoding
def encoding_comment():
return '# -*- coding: %s -*-\n' % file_encoding
def write(data):
if type(data) == unicode:
data = data.encode(output_encoding())
output.write(data)
def write_stderr(data):
if type(data) == unicode:
data = data.encode(output_encoding())
sys.stderr.write(data)
debugging = True
def debug(msg):
if debugging:
write_stderr('DEBUG: %s\n' % msg)
def tmpfile_name():
if output == sys.stdout:
raise WorblehatException('tmpfile_name: No file open')
return output.name
class tmpfile:
def __init__(self, prefix):
self.prefix = prefix
def __enter__(self):
open_tmpfile(self.prefix)
def __exit__(self, exc_type, exc, traceback):
close_tmpfile()
def run_editor(filename):
if os.path.exists(filename):
os.system("%s %s || /usr/bin/env vi %s" %
(os.getenv("EDITOR"), filename, filename))
else:
exit("Error: %s: File does not exist!" % filename)

View File

@ -1,6 +1,5 @@
import re import re
import types import types
from exc import WorblehatException
# The possible fields for each type of object. # The possible fields for each type of object.
# #
@ -53,13 +52,10 @@ action_fields = {
{ 'type': 'category', { 'type': 'category',
'required': ['id'] } } 'required': ['id'] } }
class CommitFormatSyntaxError(WorblehatException): class CommitFormatSyntaxError(Exception):
def __init__(self, msg, linenr): def __init__(self, msg, linenr):
WorblehatException.__init__(self, 'Syntax error on line %d: %s' % (linenr, msg)) super(CommitFormatSyntaxError, self).__init__(self, 'Syntax error on line %d: %s' %
(linenr, msg))
class CommitFormatError(WorblehatException):
def __init__(self, msg):
WorblehatException.__init__(self, msg)
def read_field_value_str(val): def read_field_value_str(val):
if val.strip() == '': if val.strip() == '':
@ -69,10 +65,7 @@ def read_field_value_str(val):
def read_field_value_int(val): def read_field_value_int(val):
if val.strip() == '': if val.strip() == '':
return None return None
try: return int(val.strip())
return int(val.strip())
except ValueError, TypeError:
raise WorblehatException('%s is not an integer' % val)
def read_field_value_dict(val): def read_field_value_dict(val):
d = {} d = {}
@ -178,27 +171,23 @@ def write_field_value_str(val):
lines = '' lines = ''
if not val: if not val:
val = '' val = ''
val = unicode(val)
value_lines = val.split('\n') value_lines = val.split('\n')
for l in value_lines: for l in value_lines:
lines += ' ' + l + '\n' lines += ' ' + l + '\n'
if len(value_lines) > 1:
lines = '\n' + lines
return lines return lines
def write_field_value_int(val):
return ' %s\n' % str(val)
def write_field_value_dict(val): def write_field_value_dict(val):
lines = '\n' lines = '\n'
for (key,values) in val.items(): for (key,values) in val.items():
for single_value in values: for single_value in values:
lines += ' ' + key + ' ' + single_value + '\n' lines += ' ' + key + ' ' + unicode(single_value) + '\n'
return lines return lines
def write_field_value_list(val): def write_field_value_list(val):
lines = '' lines = ''
for single_value in val: for single_value in val:
lines += ' ' + single_value lines += ' ' + unicode(single_value)
return lines return lines
def make_comment(s): def make_comment(s):
@ -208,7 +197,7 @@ def make_comment(s):
def write_action(d): def write_action(d):
if type(d) in types.StringTypes: if type(d) in types.StringTypes:
return make_comment(d) return make_comment(d)
lines = u'' lines = ''
if 'comment' in d: if 'comment' in d:
lines += make_comment(d['comment']) lines += make_comment(d['comment'])
action = d['action'] action = d['action']
@ -217,7 +206,7 @@ def write_action(d):
for field, ftype in fields[data_type]: for field, ftype in fields[data_type]:
if field in d: if field in d:
value_writer = {'s': write_field_value_str, value_writer = {'s': write_field_value_str,
'i': write_field_value_int, 'i': write_field_value_str,
'd': write_field_value_dict, 'd': write_field_value_dict,
'l': write_field_value_list}[ftype] 'l': write_field_value_list}[ftype]
lines += field + ':' + value_writer(d[field]) lines += field + ':' + value_writer(d[field])

View File

@ -1,32 +1,12 @@
db_encoding = 'utf8' import os
import tempfile
def value_to_db(value):
if type(value) == unicode:
return value.encode(db_encoding)
return value
def value_from_db(value):
if type(value) == str:
return unicode(value, db_encoding)
return value
def execute_query(cursor, query, bindings): def execute_query(cursor, query, bindings):
for (key, val) in bindings.items(): for (key, val) in bindings.items():
if val == None: if val == None:
query = query.replace('%(' + key + ')d', 'NULL') query = query.replace('%(' + key + ')d', 'NULL')
bindings = map_dict(value_to_db, bindings)
cursor.execute(query, bindings) cursor.execute(query, bindings)
def fetchone(cursor):
a = cursor.fetchone()
if a != None:
return map(value_from_db, a)
return None
def fetchall(cursor):
return map(lambda row: map(value_from_db, row),
cursor.fetchall())
def make_result_dict(cursor, row): def make_result_dict(cursor, row):
d = {} d = {}
for i in xrange(len(row)): for i in xrange(len(row)):
@ -34,14 +14,14 @@ def make_result_dict(cursor, row):
return d return d
def fetchone_dict(cursor): def fetchone_dict(cursor):
row = fetchone(cursor) row = cursor.fetchone()
if row != None: if row != None:
return make_result_dict(cursor, row) return make_result_dict(cursor, row)
return None return None
def fetchall_dict(cursor): def fetchall_dict(cursor):
return map(lambda r: make_result_dict(cursor, r), return map(lambda r: make_result_dict(cursor, r),
fetchall(cursor)) cursor.fetchall())
def first(lst): def first(lst):
return lst[0] return lst[0]
@ -76,12 +56,6 @@ def mapcond(fun, predicate, lst):
return x return x
return map(mapfun, lst) return map(mapfun, lst)
def map_dict(fun, d):
res = {}
for key in d:
res[key] = fun(d[key])
return res
def maptup(fun, lst): def maptup(fun, lst):
return tuple(map(fun, lst)) return tuple(map(fun, lst))
@ -110,3 +84,16 @@ def combine_dicts(*dicts):
res.update(d) res.update(d)
return res return res
def run_editor(filename):
if os.path.exists(filename):
os.system("%s %s || /usr/bin/env vi %s" %
(os.getenv("EDITOR"), filename, filename))
else:
exit("Error: %s: File does not exist!" % filename)
def write_tmpfile(pfix, content, encoding='utf8'):
file = tempfile.NamedTemporaryFile(prefix=pfix+'-', dir='/tmp', delete=False)
file.write(content.encode(encoding))
name = file.name
file.close()
return name

View File

@ -7,8 +7,6 @@ import pgdb
from fileformat import read_actionlist, write_actionlist from fileformat import read_actionlist, write_actionlist
from google_interface import google_suggest_book_data from google_interface import google_suggest_book_data
from util import * from util import *
from exc import WorblehatException
from file_io import tmpfile, tmpfile_name, write, write_stderr, debug, run_editor, encoding_comment
# connection = pgdb.connect(database='oysteini_pbb2', # connection = pgdb.connect(database='oysteini_pbb2',
# user='oysteini_pbb', # user='oysteini_pbb',
@ -59,8 +57,6 @@ q_edit_person = \
'UPDATE person ' \ 'UPDATE person ' \
'SET lastname=%(lastname)s, firstname=%(firstname)s ' \ 'SET lastname=%(lastname)s, firstname=%(firstname)s ' \
'WHERE id=%(id)s' 'WHERE id=%(id)s'
q_delete_person = \
'DELETE FROM person WHERE id=%(id)s'
q_new_book = \ q_new_book = \
'INSERT INTO book ' \ 'INSERT INTO book ' \
' (isbn, id, title, subtitle, category, publisher, ' \ ' (isbn, id, title, subtitle, category, publisher, ' \
@ -89,7 +85,7 @@ q_edit_category = \
'WHERE id=%(id)s' 'WHERE id=%(id)s'
q_add_bookreference = \ q_add_bookreference = \
'INSERT INTO bookreference (book, reftype, value) ' \ 'INSERT INTO bookreference (book, reftype, value) ' \
'VALUES (%(isbn)s, %(reftype)s, %(value)s)' 'VALUES (%(isbn)s, %(reftype)s, %(value))'
def connect_to_db(): def connect_to_db():
connection = pgdb.connect(database='oysteini_pbb2', connection = pgdb.connect(database='oysteini_pbb2',
@ -106,7 +102,7 @@ def get_by_id(connection, id):
for (typ,q) in [('book', q_book), for (typ,q) in [('book', q_book),
('person', q_person), ('person', q_person),
('category', q_cat)]: ('category', q_cat)]:
execute_query(c, q, {'id': id}) c.execute(q, {'id': id})
if c.rowcount > 0: if c.rowcount > 0:
d = fetchone_dict(c) d = fetchone_dict(c)
d['type'] = typ d['type'] = typ
@ -218,16 +214,15 @@ def show(connection, ids, commit_format=False, tmp_file=False):
objects[i] = show_fun(objects[i]) objects[i] = show_fun(objects[i])
if commit_format: if commit_format:
output = encoding_comment() + write_actionlist(objects) output = write_actionlist(objects)
else: else:
output = '\n'.join(objects) output = '\n'.join(objects)
if tmp_file: if tmp_file:
with tmpfile('.'.join(ids)): filename = write_tmpfile('.'.join(ids), output)
write_stderr('%s\n' % tmpfile_name()) print filename
write(output) return filename
return tmpfile_name()
else: else:
write(output) print output.strip()
def list_books(connection): def list_books(connection):
c = connection.cursor() c = connection.cursor()
@ -235,7 +230,7 @@ def list_books(connection):
#print fetchall_dict(c) #print fetchall_dict(c)
for i in xrange(c.rowcount): for i in xrange(c.rowcount):
book = fetchone_dict(c) book = fetchone_dict(c)
write('%-13s %-10s %-60s %s\n' % print('%-13s %-10s %-60s %s' %
(book['isbn'], str_or_empty(book['id']), (book['isbn'], str_or_empty(book['id']),
cut_str(book['title'], 60), book['persons'])) cut_str(book['title'], 60), book['persons']))
@ -244,16 +239,16 @@ def list_persons(connection):
c.execute(q_list_persons) c.execute(q_list_persons)
for i in xrange(c.rowcount): for i in xrange(c.rowcount):
person = fetchone_dict(c) person = fetchone_dict(c)
write('%-5s %-30s %d books\n' % (person['id'], print '%-5s %-30s %d books' % (person['id'],
person['firstname']+' '+person['lastname'], person['firstname']+' '+person['lastname'],
person['num_books'])) person['num_books'])
def list_categories(connection): def list_categories(connection):
c = connection.cursor() c = connection.cursor()
c.execute(q_list_categories) c.execute(q_list_categories)
for i in xrange(c.rowcount): for i in xrange(c.rowcount):
cat = fetchone_dict(c) cat = fetchone_dict(c)
write('%-15s %-30s %d books\n' % (cat['id'], cat['name'], cat['num_books'])) print '%-15s %-30s %d books' % (cat['id'], cat['name'], cat['num_books'])
def list_cmd(connection, what): def list_cmd(connection, what):
funs = { 'book': list_books, funs = { 'book': list_books,
@ -265,11 +260,9 @@ def list_cmd(connection, what):
def search_book(connection, search_strings, search_description=False): def search_book(connection, search_strings, search_description=False):
c = connection.cursor() c = connection.cursor()
if search_description: if search_description:
where_clauses = ['book.title ILIKE %s OR book.subtitle ILIKE %s OR book.series ILIKE %s \ where_clauses = ['book.title ILIKE %s OR book.subtitle ILIKE %s OR book.series ILIKE %s OR person.lastname ILIKE %s OR person.firstname ILIKE %s OR book.description ILIKE %s']*len(search_strings)
OR person.lastname ILIKE %s OR person.firstname ILIKE %s OR book.description ILIKE %s']*len(search_strings)
else: else:
where_clauses = ['book.title ILIKE %s OR book.subtitle ILIKE %s OR book.series ILIKE %s \ where_clauses = ['book.title ILIKE %s OR book.subtitle ILIKE %s OR book.series ILIKE %s OR person.lastname ILIKE %s OR person.firstname ILIKE %s']*len(search_strings)
OR person.lastname ILIKE %s OR person.firstname ILIKE %s']*len(search_strings)
result_list = [] result_list = []
for s in search_strings: for s in search_strings:
@ -279,18 +272,10 @@ def search_book(connection, search_strings, search_description=False):
else: else:
for i in range(5): for i in range(5):
result_list.append(s) result_list.append(s)
c.execute('SELECT isbn,book.id AS id,title,category, \ c.execute('SELECT * FROM book LEFT JOIN bookperson ON book.isbn=bookperson.book LEFT JOIN person ON person.id=bookperson.person WHERE ' + ' OR '.join(where_clauses), map(lambda s:'%' + s + '%',result_list))
array_to_string(array_agg(person.lastname || \' (\' || person.id || \')\'), \', \') AS persons \
FROM book LEFT JOIN bookperson ON book.isbn=bookperson.book \
LEFT JOIN person ON person.id=bookperson.person \
WHERE ' + ' OR '.join(where_clauses) + '\
GROUP BY isbn, book.id, title, category \
', map(lambda s:'%' + s + '%',result_list))
for i in xrange(c.rowcount): for i in xrange(c.rowcount):
book = fetchone_dict(c) book = fetchone_dict(c)
write('%-13s %-10s %-60s %s' % print book['isbn'], book['title'], book['person']
(book['isbn'], str_or_empty(book['id']),
cut_str(book['title'], 60), book['persons']))
def search_person(connection, search_strings): def search_person(connection, search_strings):
@ -299,14 +284,13 @@ def search_person(connection, search_strings):
for s in search_strings: for s in search_strings:
for i in range(3): for i in range(3):
result_strings.append(s) result_strings.append(s)
c.execute('SELECT * FROM person LEFT JOIN bookperson ON person.id=bookperson.person \ c.execute('SELECT * FROM person LEFT JOIN bookperson ON person.id=bookperson.person WHERE person.lastname ILIKE %s or person.firstname ILIKE %s OR person.id ILIKE %s', result_strings)
WHERE person.lastname ILIKE %s or person.firstname ILIKE %s OR person.id ILIKE %s', result_strings)
for i in xrange(c.rowcount): for i in xrange(c.rowcount):
person = fetchone_dict(c) person = fetchone_dict(c)
write(person['lastname'], ', ', person['firstname'], '\t', person['book']) print person['lastname'], ', ', person['firstname'], '\t', person['book']
def do_action(connection, action): def do_action(connection, action):
debug('ACTION %s ' % action) print 'ACTION %s ' % action
c = connection.cursor() c = connection.cursor()
queries = {'new-person': q_new_person, queries = {'new-person': q_new_person,
'edit-person': q_edit_person, 'edit-person': q_edit_person,
@ -317,9 +301,9 @@ def do_action(connection, action):
action_type = action['action'] action_type = action['action']
execute_query(c, queries[action_type], action) execute_query(c, queries[action_type], action)
if action_type in ['new-book', 'edit-book']: if action_type in ['new-book', 'edit-book']:
debug('FIXING PERSONS: REMOVING') print 'FIXING PERSONS: REMOVING'
c.execute(q_remove_bookpersons, {'isbn': action['isbn']}) c.execute(q_remove_bookpersons, {'isbn': action['isbn']})
debug('FIXING PERSONS: ADDING') print 'FIXING PERSONS: ADDING'
if action['persons']: if action['persons']:
for (relation, personlist) in action['persons'].items(): for (relation, personlist) in action['persons'].items():
for person in personlist: for person in personlist:
@ -329,23 +313,30 @@ def do_action(connection, action):
'relation': relation}) 'relation': relation})
if action['references']: if action['references']:
c.execute('SELECT referencetype.id FROM referencetype') c.execute('SELECT referencetype.id FROM referencetype')
legal_reftypes = [a for list in c.fetchall() for a in list] refs = c.fetchone()
for (reftype, reflist) in action['references'].items(): for (reftype, reflist) in action['references'].items():
for ref in reflist: for ref in reflist:
if reftype in legal_reftypes: if ref in refs:
c.execute(q_add_bookreference, c.execute(q_add_reference,
{'isbn': action['isbn'], {'isbn': action['isbn'],
'reftype': reftype, 'reftype': reftype,
'value': ref}) 'value': ref})
else: else:
raise WorblehatException('%s is not in the defined references, please use a more general one' % reftype) raise WorblehatException('%s is not in the defined references, please use a more general one' % reftype)
class WorblehatException(Exception):
def __init__(self, msg):
Exception.__init__(self, msg)
def commit_actions(connection, actions): def commit_actions(connection, actions):
for action in actions: for action in actions:
try: try:
do_action(connection, action) do_action(connection, action)
except pgdb.DatabaseError, err: except pgdb.DatabaseError, err:
raise WorblehatException('commit: Error in "%s" action: %s' % (action['action'], err)) print>>sys.stderr, 'Error in "%s" action: %s' % (action['action'], err)
print>>sys.stderr, 'Giving up.'
return
connection.commit() connection.commit()
def commit(connection, filename=None): def commit(connection, filename=None):
@ -355,7 +346,9 @@ def commit(connection, filename=None):
text = f.read() text = f.read()
f.close() f.close()
except IOError, e: except IOError, e:
raise WorblehatException('commit: Error reading file %s: %s' % (filename, e)) print 'commit: Error reading file %s: %s' % (filename, e)
print 'Exiting.'
sys.exit(1)
else: else:
text = sys.stdin.read() text = sys.stdin.read()
@ -364,17 +357,9 @@ def commit(connection, filename=None):
def edit(connection, ids): def edit(connection, ids):
filename = show(connection, ids, commit_format=True, tmp_file=True) filename = show(connection, ids, commit_format=True, tmp_file=True)
done = False print filename
while not done: run_editor(filename)
run_editor(filename) commit(connection, filename)
try:
commit(connection, filename)
done = True
except WorblehatException, exc:
write_stderr('%s\n' % exc)
answer = raw_input('Retry? [Y/n] ')
if answer == 'n':
done = True
def map_cmd(connection, shelfname=None, category=None): def map_cmd(connection, shelfname=None, category=None):
pass pass
@ -382,15 +367,17 @@ def map_cmd(connection, shelfname=None, category=None):
def suggest_book_data(connection, tmp_file=False): def suggest_book_data(connection, tmp_file=False):
return google_suggest_book_data(connection, tmp_file) return google_suggest_book_data(connection, tmp_file)
<<<<<<< .mine
def register_books(connection): def register_books(connection):
filename = suggest_book_data(connection, tmp_file=True) filename = suggest_book_data(connection, tmp_file=True)
#print("Tempfile filename: " + filename)
run_editor(filename) run_editor(filename)
commit(connection, filename) commit(connection, filename)
=======
def give_bananas(): def give_bananas():
write("Om nom nom... Thanks!") print "Om nom nom... Thanks!"
>>>>>>> .r164
commands = { 'show': commands = { 'show':
{ 'args': [('ids', (1,None))], { 'args': [('ids', (1,None))],
'options': ['commit_format', 'tmp_file'], 'options': ['commit_format', 'tmp_file'],

View File

@ -1,53 +0,0 @@
Hei,
i går startet vi opp med planleggingen av det nye biblioteksystemet. Det
har fått navnet Worblehat, og mailinglista worblehat@pvv.ntnu.no er
opprettet. Siden det fremdeles ikke er så mange som har meldt seg på
der, spammer jeg denne også til aktive.
Det er dessuten sendt mail til drift, om å legge oss ut på dev.
Vi har laget et foreløpig design av både systemet og databasen. Disse er
foreløpig å finne på http://www.pvv.ntnu.no/~tirilane/worblehat.
Rettelser og forbedringer mottas med takk. Det hadde vært spesielt fint
om noen med peiling på databaser kunne gå gjennom worblehat-db.dia og
fikse. Og det hadde vært enda bedre om noen med peiling på databaser
kunne implementere databasen, og legge inn alle de magiske triksene for
å få til til å gå raskt.
Ellers er designet ganske enkelt. Vi trenger en database, og noen
klienter. Vi trenger blant annet en for registrering av nye bøker. Den
skal ha to modus:
1) Registrering av en bok
2) Registrering av mange bøker
Vi tenkte at 1) kunne være sånn fin og brukervennlig, mens 2) skal gå
raskt for mange bøker. Gjerne slik:
Blippe bok, klienten plukker opp ISBN-nummeret, spør ISBNdb.com, og
registrerer boka i databasen om den ble funnet. Ble den ikke funnet, må
den på en eller annen måte si fra, slik at vi kan ta boka manuelt. Dette
kan løses ved å samle opp ISBN-nummeret, men siden det å manuelt lete
etter bøker med et gitt ISBN-nummer ikke akkurat er lett, hadde det nok
vært bedre om den stoppet opp eller noe.
Dessuten trenger vi noe for å spørre databasen. Her tenkte vi CHI,
IRC-bot og noe web-basert. Strengt tatt trenger vi bare en, og da bør
kanskje CLI ha hovedprioritet.
For at en eventuell IRC-bot ikke skal gjøre for mye arbeid, tenkte vi å
ha et lag mellom boten og databasen. Denne skal implementere
søke-funksjonene, som boten bare trenger å kalle. De andre (CLI og web)
står også fritt til å benytte seg av dette. Det blir opp til de som
implementerer. Fordelen er mindre duplisering av kode. Ulempen er at da
må alle skrives i samme språk. Men opp til implementørene.
En ting vi ikke har bestemt, er om plasseringen i hylla skal baseres på
Deweys system eller UDC. Fordelen med Dewey er at nummeret kan hentes
ned fra ISBNdb.com sammen med resten av informasjonene. Fordelen med UDC
er at det er mye mer spesialisert, hvilket kan være en stor fordel når
vi har så mange bøker om et såpass smalt felt. Dessuten bruker Teknisk
hovedbibliotek UDC. Hva synes man?
http://en.wikipedia.org/wiki/Universal_Decimal_Classification
http://en.wikipedia.org/wiki/Dewey_Decimal_Classification
Og hvem har lyst til å fikse hva? Noen frivillige?

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -1,4 +0,0 @@
# Worblehat FAQ
Q: Hvor kommer navnet fra?
A: Discworld, bibliotekarens tidligere navn. Se https://wiki.lspace.org/Horace_Worblehat

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB