Merge branch 'master' into edumed
authorMarcin Koziej <marcin.koziej@nowoczesnapolska.org.pl>
Wed, 28 Nov 2012 14:50:00 +0000 (15:50 +0100)
committerMarcin Koziej <marcin.koziej@nowoczesnapolska.org.pl>
Wed, 28 Nov 2012 14:50:00 +0000 (15:50 +0100)
13 files changed:
apps/catalogue/management/__init__.py
apps/catalogue/management/commands/import_pad.py [new file with mode: 0644]
apps/catalogue/management/edumed.py [new file with mode: 0644]
apps/catalogue/tests/edumed.py [new file with mode: 0644]
apps/catalogue/tests/files/gim_3.1.txt [new file with mode: 0644]
redakcja/settings/compress.py
redakcja/static/css/html.css
redakcja/static/js/wiki/xml-tools.coffee [new file with mode: 0644]
redakcja/static/js/wiki/xml-tools.js [new file with mode: 0644]
redakcja/static/js/wiki/xslt.js
redakcja/static/xsl/wl2html_client.xsl
requirements.txt
scripts/make-tags [new file with mode: 0755]

index f7731d7..6bb2047 100644 (file)
@@ -120,3 +120,9 @@ class XmlUpdater(object):
         """Prints the counters."""
         for item in sorted(self.counters.items()):
             print "%s: %d" % item
+
+
+auto_taggers = {}
+from . import edumed
+auto_taggers['edumed'] = edumed.tagger
+
diff --git a/apps/catalogue/management/commands/import_pad.py b/apps/catalogue/management/commands/import_pad.py
new file mode 100644 (file)
index 0000000..dbfe799
--- /dev/null
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+from slughifi import slughifi
+from collections import defaultdict
+import json
+from optparse import make_option
+import urllib2
+
+from py_etherpad import EtherpadLiteClient
+from django.core.management.base import BaseCommand
+from django.core.management.color import color_style
+from django.db import transaction
+from librarian.dcparser import BookInfo
+from librarian import ParseError, ValidationError, WLURI
+from django.conf import settings
+from catalogue.models import Book
+from catalogue.management import auto_taggers
+import re
+
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
+            help='Less output'),
+        make_option('-p', '--pad', dest='pad_id', help='Pad Id (or many id\'s, comma separated)'),
+        make_option('-P', '--pad-ids', dest='pad_ids_file', help='Read Pad id\'s from file'),
+        make_option('-E', '--edumed', dest="tag_edumed", default=False,
+                    action='store_true', help="Perform EduMed pre-tagging"),
+        make_option('-a', '--autotagger', dest="auto_tagger", default=None, help="Use auto-tagger (one of: %s)" % ', '.join(auto_taggers.keys())),
+        make_option('-S', '--use-pad-prefix', dest="pad_prefix", default=False, action='store_true', help="use pad name prefix in slug"),
+    )
+    help = 'Imports Text files from EtherPad Lite.'
+
+    def handle(self, *args, **options):
+
+        self.style = color_style()
+
+        verbose = options.get('verbose')
+        pad_ids_file = options.get('pad_ids_file')
+        if pad_ids_file:
+            pad_id = open(pad_ids_file).readlines()
+        else:
+            pad_id = options.get("pad_id").split(',')
+        pad_id = map(str.strip, pad_id)
+
+        # Start transaction management.
+        transaction.commit_unless_managed()
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+
+        if verbose:
+            print 'Reading currently managed files (skipping hidden ones).'
+        slugs = defaultdict(list)
+        for b in Book.objects.exclude(slug__startswith='.').all():
+            if verbose:
+                print b.slug
+            text = b.materialize().encode('utf-8')
+
+            try:
+                info = BookInfo.from_string(text)
+                slugs[info.url.slug].append(b)
+            except (ParseError, ValidationError):
+                slugs[b.slug].append(b)
+
+        book_count = 0
+        commit_args = {
+            "author_name": 'Platforma',
+            "description": 'Automatycznie zaimportowane z EtherPad',
+            "publishable": False,
+        }
+
+        if verbose:
+            print 'Opening Pad'
+        pad = EtherpadLiteClient(settings.ETHERPAD_APIKEY, settings.ETHERPAD_URL)
+
+        for pid in pad_id:
+            try:
+                text = pad.getText(pid)['text']
+            except ValueError:
+                print "pad '%s' does not exist" % pid
+                continue
+
+            open("/tmp/pad_%s.txt" % pid, 'w').write(text.encode('utf-8'))
+            
+            if options.get('tag_edumed'):
+                auto_tagger = 'edumed'
+            else:
+                auto_tagger = options.get('auto_tagger')
+            if auto_tagger:
+                text = auto_taggers[auto_tagger](text)
+            try:
+                info = BookInfo.from_string(text.encode('utf-8'))
+                slug = info.url.slug
+            except (ParseError, ValidationError):
+                slug = slughifi(pid)
+
+            print "Importing %s (slug %s)..." % (pid, slug)
+            title = pid
+
+            #            print slugs, slug
+            previous_books = slugs.get(slug)
+            if previous_books:
+                if len(previous_books) > 1:
+                    print self.style.ERROR("There is more than one book "
+                        "with slug %s:" % slug),
+                previous_book = previous_books[0]
+                comm = previous_book.slug
+            else:
+                previous_book = None
+                comm = '*'
+            print book_count, slug, '-->', comm
+
+            # add pad prefix now.
+            if options.get('pad_prefix'):
+                pad_prefix = re.split(r"[-_]", pid)[0]
+                slug = pad_prefix + "-" + slug
+                
+            if previous_book:
+                book = previous_book
+                book.slug = slug
+            else:
+                book = Book()
+                book.slug = slug
+            book.title = title
+            book.save()
+
+            if len(book) > 0:
+                chunk = book[0]
+                chunk.slug = slug[:50]
+                chunk.title = title[:255]
+                chunk.save()
+            else:
+                chunk = book.add(slug, title)
+
+            chunk.commit(text, **commit_args)
+
+            book_count += 1
+
+        # Print results
+        print
+        print "Results:"
+        print "Imported %d books from Pad" % book_count
+
+        transaction.commit()
+        transaction.leave_transaction_management()
diff --git a/apps/catalogue/management/edumed.py b/apps/catalogue/management/edumed.py
new file mode 100644 (file)
index 0000000..56088fe
--- /dev/null
@@ -0,0 +1,545 @@
+# EduMed auto-tagger
+# -*- coding: utf-8 -*-
+import re
+from slughifi import slughifi
+
+
+class Tagger(object):
+    def __init__(self, state, lines):
+        self.state = state
+        self.lines = lines
+
+    def spawn(self, cls):
+        return cls(self.state, self.lines)
+
+    def line(self, position):
+        return self.lines[position]
+
+    ignore = [re.compile(r"^[\[][PA][\]] - [^ ]+$")]
+    empty_line = re.compile(r"^\s+$")
+
+    def skip_empty(self, position):
+        while self.line(position) == "" or \
+            self.empty_line.match(self.line(position)) or \
+            filter(lambda r: r.match(self.line(position)),
+                             self.ignore[:]):
+            position += 1
+        return position
+
+    def tag(self, position):
+        """
+Return None -- means that we can't tag it in any way
+        """
+        return None
+
+    def wrap(self, tagname, content):
+        return u"<%s>%s</%s>" % (tagname, content, tagname)
+
+    @staticmethod
+    def anymatches(regex):
+        return lambda x: regex.match(x)
+
+
+class Section(Tagger):
+    looks_like = re.compile(r"^[IVX]+[.]\s+(.*)$")
+
+    def __init__(self, *a):
+        super(Section, self).__init__(*a)
+        self.is_podrozdzial = False
+
+    def tag(self, pos):
+        pos2 = self.skip_empty(pos)
+        pos = pos2
+        m = self.looks_like.match(self.line(pos))
+        if m:
+            self.title = m.groups()[0]
+            return pos + 1
+
+    def __unicode__(self):
+        return self.wrap(self.is_podrozdzial and "naglowek_podrozdzial" or "naglowek_rozdzial",
+                         self.title)
+
+
+class Meta(Tagger):
+    looks_like = re.compile(r"([^:]+): (.*)", re.UNICODE)
+
+    def tag(self, pos):
+        pos = self.skip_empty(pos)
+        m = self.looks_like.match(self.line(pos))
+        if m:
+            k = m.groups()[0]
+            v = m.groups()[1]
+            m = self.state.get('meta', {})
+            m[k] = v
+            self.state['meta'] = m
+            return pos + 1
+
+
+class Informacje(Tagger):
+    def tag(self, pos):
+        self.title = self.spawn(Section)
+        self.meta = []
+        pos = self.title.tag(pos)
+        if pos is None: return
+
+            # collect meta
+        while True:
+            pos = self.skip_empty(pos)
+            meta = self.spawn(Meta)
+            pos2 = meta.tag(pos)
+            if pos2 is None: break
+            self.meta.append(meta)
+            pos = pos2
+
+        return pos
+
+
+class List(Tagger):
+    point = re.compile(r"^[\s]*[-*·]{1,2}(.*)")
+    num = re.compile(r"^[\s]*[a-z][.]\s+(.*)")
+
+    def __init__(self, *args):
+
+        super(List, self).__init__(*args)
+        self.items = []
+        self.type = 'punkt'
+
+    def tag(self, pos):
+        while True:
+            l = self.line(pos)
+            m = self.point.match(l)
+            if not m:
+                m = self.num.match(l)
+                if m: self.type = 'num'
+            if l and m:
+                self.items.append(m.groups()[0].lstrip())
+                pos += 1
+            else:
+                break
+        if self.items:
+            return pos
+
+    def append(self, tagger):
+        self.items.append(tagger)
+
+    def __unicode__(self):
+        s = '<lista typ="%s">' % self.type
+        for i in self.items:
+            if isinstance(i, list):
+                x = "\n".join(map(lambda elem: unicode(elem), i))
+            else:
+                x = unicode(i)
+            s += "\n<punkt>%s</punkt>" % x
+        s += "\n</lista>\n"
+        return s
+
+
+class Paragraph(Tagger):
+    remove_this = [
+        re.compile(r"[\s]*opis zawarto.ci[\s]*", re.I),
+        re.compile(r"^[\s]*$"),
+        re.compile(r"http://pad.nowoczesnapolska.org.pl/p/slowniczek")
+        ]
+    podrozdzial = [
+        re.compile(r"[\s]*(przebieg zaj..|opcje dodatkowe)[\s]*", re.I),
+        ]
+
+    def tag(self, pos):
+        self.line = self.lines[pos]
+        self.ignore = False
+        self.is_podrozdzial = False
+
+        for x in self.remove_this:
+            if x.match(self.line):
+                self.ignore = True
+
+        for x in self.podrozdzial:
+            if x.match(self.line):
+                self.is_podrozdzial = True
+
+        return pos + 1
+
+    def __unicode__(self):
+        if not self.ignore:
+            if self.is_podrozdzial:
+                tag = 'naglowek_podrozdzial'
+            else:
+                tag = 'akap'
+            return u"<%s>%s</%s>" % (tag, self.line, tag)
+        else:
+            return u''
+
+
+class Container:
+    def __init__(self, tag_name, *elems):
+        self.tag_name = tag_name
+        self.elems = elems
+
+    def __unicode__(self):
+        s = u"<%s>" % self.tag_name
+        add_nl = False
+        for e in self.elems:
+            if isinstance(e, (str, unicode)):
+                s += unicode(e)
+            else:
+                s += "\n  " + unicode(e)
+                add_nl = True
+
+        if add_nl: s += "\n"
+        s += u"</%s>" % self.tag_name
+        return s
+
+
+def eatany(pos, *taggers):
+    try:
+        for t in list(taggers):
+            p = t.tag(pos)
+            if p:
+                return (t, p)
+    except IndexError:
+        pass
+    return (None, pos)
+
+
+def eatseq(pos, *taggers):
+    good = []
+    taggers = list(taggers[:])
+    try:
+        while len(taggers):
+            p = taggers[0].tag(pos)
+            if p is None:
+                return (tuple(good), pos)
+            good.append(taggers.pop(0))
+            # print "%d -> %d" % (pos, p)
+            pos = p
+
+    except IndexError:
+        print "Got index error for pos=%d" % pos
+    return (tuple(good), pos)
+
+
+def tagger(text, pretty_print=False):
+    """
+tagger(text) function name and signature is a contract.
+returns auto-tagged text
+    """
+    if not isinstance(text, unicode):
+        text = unicode(text.decode('utf-8'))
+    lines = text.split("\n")
+    pos = 0
+    content = []
+    state = {}
+    info = Informacje(state, lines)
+
+    ((info,), pos) = eatseq(pos, info)
+
+    # print "[i] %d. %s" % (pos, lines[pos])
+
+    content.append(info)
+
+    while True:
+        x, pos = eatany(pos, info.spawn(Section),
+                        info.spawn(List), info.spawn(Paragraph))
+
+        if x is not None:
+            content.append(x)
+        else:
+            content.append(lines[pos])
+            pos += 1
+            if pos >= len(lines):
+                break
+
+    return toxml(content, pretty_print=pretty_print)
+
+dc_fixed = {
+    'description': u'Publikacja zrealizowana w ramach projektu Cyfrowa Przyszłość (http://edukacjamedialna.edu.pl).',
+    'relation': u'moduły powiązane linki',
+    'description.material': u'linki do załączników',
+    'rights': u'Creative Commons Uznanie autorstwa - Na tych samych warunkach 3.0',
+    }
+
+
+class NotFound(Exception):
+    pass
+
+
+def find_block(content, title_re, begin=-1, end=-1):
+    title_re = re.compile(title_re, re.I | re.UNICODE)
+
+    rb = -1
+    if begin < 0: begin = 0
+    if end < 0: end = len(content)
+
+    for i in range(begin, end):
+        elem = content[i]
+        if isinstance(elem, Paragraph):
+            if title_re.match(elem.line):
+                rb = i
+                continue
+        if isinstance(elem, Section):
+            if title_re.match(elem.title):
+                rb = i
+                continue
+        if rb >= 0:
+            if isinstance(elem, List):
+                continue
+            if isinstance(elem, Paragraph) and elem.line:
+                continue
+            break
+    if rb >= 0:
+        return rb, i
+    raise NotFound()
+
+
+def remove_block(content, title_re, removed=None):
+    rb, re = find_block(content, title_re)
+    if removed is not None and isinstance(removed, list):
+        removed += content[rb:re][:]
+    content[rb:re] = []
+    return content
+
+
+def mark_activities(content):
+    i = 0
+    tl = len(content)
+    is_przebieg = re.compile(r"[\s]*przebieg zaj..[\s]*", re.I)
+
+    is_next_section = re.compile(r"^[IVX]+[.]? ")
+    is_activity = re.compile(r"^[0-9]+[.] (.+)")
+
+    is_activity_tools = re.compile(r"^pomoce:[\s]*(.+)")
+    is_activity_work = re.compile(r"^forma pracy:[\s]*(.+)")
+    is_activity_time = re.compile(r"^czas:[\s]*([\d]+).*")
+    activity_props = {
+        'pomoce': is_activity_tools,
+        'forma': is_activity_work,
+        'czas': is_activity_time
+        }
+    activities = []
+
+    in_activities = False
+    ab = -1
+    ae = -1
+    while True:
+        e = content[i]
+        if isinstance(e, Section):
+            if in_activities and \
+                is_next_section.match(e.title):
+                in_activities = False
+            
+        if isinstance(e, Paragraph):
+            if not in_activities and \
+                is_przebieg.match(e.line):
+                in_activities = True
+
+            if in_activities:
+                m = is_activity.match(e.line)
+                if m:
+                    e.line = m.groups()[0]
+                    ab = i
+                if is_activity_time.match(e.line):
+                    ae = i + 1
+                    activities.append((ab, ae))
+        i += 1
+        if i >= tl: break
+
+    activities.reverse()
+    for ab, ae in activities:
+        act_len = ae - ab
+        info_start = ae
+
+        act_els = []
+        act_els.append(Container("opis", content[ab]))
+        for i in range(ab, ae):
+            e = content[i]
+            if isinstance(e, Paragraph):
+                for prop, pattern in activity_props.items():
+                    m = pattern.match(e.line)
+                    if m:
+                        act_els.append(Container(prop, m.groups()[0]))
+                        if info_start > i: info_start = i
+        act_els.insert(1, Container('wskazowki',
+                                    *content[ab + 1:info_start]))
+        content[ab:ae] = [Container('aktywnosc', *act_els)]
+    return content
+
+
+def mark_dictionary(content):
+    db = -1
+    de = -1
+    i = 0
+    is_dictionary = re.compile(r"[\s]*s.owniczek[\s]*", re.I)
+    is_dictentry = re.compile(r"([^-]+) - (.+)")
+    slowniczek = content[0].spawn(List)
+    slowniczek.type = 'slowniczek'
+    while i < len(content):
+        e = content[i]
+        if isinstance(e, Section):
+            if is_dictionary.match(e.title):
+                db = i + 1
+            elif db >= 1:
+                de = i
+                content[db:de] = [slowniczek]
+                break
+        elif db >= 0:
+            if isinstance(e, Paragraph):
+                m = is_dictentry.match(e.line)
+                if m:
+                    slowniczek.append([Container('definiendum', m.groups()[0]),
+                                       Container('definiens', m.groups()[1])])
+
+                else:
+                    slowniczek.append(e)
+        i += 1
+
+    return content
+
+
+def mark_czytelnia(content):
+    db = -1
+    de = -1
+    i = 0
+    czy_czytelnia = re.compile(r"[\s]*czytelnia[\s]*", re.I)
+    czytelnia = content[0].spawn(List)
+    czytelnia.type = 'czytelnia'
+    while i < len(content):
+        e = content[i]
+        if isinstance(e, Section):
+            if czy_czytelnia.match(e.title):
+                db = i + 1
+            elif db >= 1:
+                de = i
+                content[db:de] = [czytelnia]
+                break
+        elif db >= 0:
+            if isinstance(e, Paragraph):
+                if e.line:
+                    czytelnia.append(e.line)
+        i += 1
+
+    return content
+
+
+
+def move_evaluation(content):
+    evaluation = []
+
+    content = remove_block(content, r"ewaluacja[+ PA\[\].]*", evaluation)
+    if evaluation:
+        #        print "found evaluation %s" % (evaluation,)
+        evaluation[0].is_podrozdzial = True
+        # evaluation place
+        opcje_dodatkowe = find_block(content, r"opcje dodatkowe\s*")
+        if opcje_dodatkowe:
+            #            print "putting evaluation just before opcje dodatkowe @ %s" % (opcje_dodatkowe, )
+            content[opcje_dodatkowe[0]:opcje_dodatkowe[0]] = evaluation
+        else:
+            materialy = find_block(content, r"materia.y[+ AP\[\].]*")
+            if materialy:
+                #                print "putting evaluation just before materialy @ %s" % (materialy, )
+                content[materialy[0]:materialy[0]] = evaluation
+            else:
+                print "er.. no idea where to place evaluation"
+    return content
+
+
+def toxml(content, pretty_print=False):
+    # some transformations
+    content = mark_activities(content)
+    content = mark_dictionary(content)
+    content = mark_czytelnia(content)
+    
+    try:
+        content = remove_block(content, r"wykorzyst(yw)?ane metody[+ PA\[\].]*")
+    except NotFound:
+        pass
+    try:
+        content = remove_block(content, r"(pomoce|potrzebne materia.y)[+ PA\[\]]*")
+    except NotFound:
+        pass
+    content = move_evaluation(content)
+
+    info = content.pop(0)
+
+    state = info.state
+    meta = state['meta']
+    slug = slughifi(meta.get(u'Tytuł modułu', ''))
+    holder = {}
+    holder['xml'] = u""
+
+    def p(t):
+        holder['xml'] += u"%s\n" % t
+
+    def dc(k, v):
+        p(u'<dc:%s xml:lang="pl" xmlns:dc="http://purl.org/dc/elements/1.1/">%s</dc:%s>' % (k, v, k))
+
+    def t(tag, ct):
+        p(u'<%s>%s</%s>' % (tag, ct, tag))
+
+    def a(ct):
+        if ct:
+            t(u'akap', ct)
+
+    p("<utwor>")
+    p(u'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">')
+    p(u'<rdf:Description rdf:about="http://redakcja.edukacjamedialna.edu.pl/documents/">')
+    authors = map(unicode.strip, meta[u'Autorzy'].split(u','))
+    for author in authors:
+        names = author.split(u' ')
+        lastname = names.pop()
+        names.insert(0, lastname + ",")
+        author = u' '.join(names)
+        dc(u'creator', author)
+    dc(u'title', meta.get(u'Tytuł modułu', u''))
+    dc(u'relation.isPartOf', meta.get(u'Dział', u''))
+    dc(u'publisher', u'Fundacja Nowoczesna Polska')
+    dc(u'subject.competence', meta.get(u'Wybrana kompetencja z Katalogu', u''))
+    dc(u'subject.curriculum', meta.get(u'Odniesienie do podstawy programowej', u''))
+    for keyword in meta.get(u'Słowa kluczowe', u'').split(u','):
+        keyword = keyword.strip()
+        dc(u'subject', keyword)
+    dc(u'description', dc_fixed['description'])
+    dc(u'description.material', dc_fixed['description.material'])
+    dc(u'relation', dc_fixed['relation'])
+    dc(u'identifier.url', u'http://edukacjamedialna.edu.pl/%s' % slug)
+    dc(u'rights', dc_fixed['rights'])
+    dc(u'rights.license', u'http://creativecommons.org/licenses/by-sa/3.0/')
+    dc(u'format', u'xml')
+    dc(u'type', u'text')
+    dc(u'date', u'2012-11-09')  # TODO
+    dc(u'audience', meta.get(u'Poziom edukacyjny', u''))
+    dc(u'language', u'pol')
+    p(u'</rdf:Description>')
+    p(u'</rdf:RDF>')
+
+    p(u'<powiesc>')
+    t(u'nazwa_utworu', meta.get(u'Tytuł modułu', u''))
+    #    p(u'<nota>')
+    a(u'<!-- Numer porządkowy: %s -->' % meta.get(u'Numer porządkowy', u''))
+    #    p(u'</nota>')
+
+    p(unicode(info.title))
+    for elm in content:
+        if isinstance(elm, unicode) or isinstance(elm, str):
+            a(elm)
+            continue
+        p(unicode(elm))
+
+    p(u'</powiesc>')
+    p(u'</utwor>')
+
+    if pretty_print:
+        from lxml import etree
+        from StringIO import StringIO
+        xml = etree.parse(StringIO(holder['xml']))
+        holder['xml'] = etree.tostring(xml, pretty_print=pretty_print, encoding=unicode)
+
+    return holder['xml']
+
+
+# TODO / TBD
+# ogarnąć podrozdziały
+#  Przebieg zajęć
+#  opcje dodatkowe
+# usunąć 'opis zawartości'
+# akapit łączony?
diff --git a/apps/catalogue/tests/edumed.py b/apps/catalogue/tests/edumed.py
new file mode 100644 (file)
index 0000000..c81683e
--- /dev/null
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+"""Tests for the publishing process."""
+
+from catalogue.test_utils import get_fixture
+
+from mock import patch
+from django.test import TestCase
+from django.contrib.auth.models import User
+from catalogue.models import Book
+from catalogue.management import edumed
+
+import logging
+south_logger=logging.getLogger('south')
+south_logger.setLevel(logging.INFO)
+
+
+class EduMedTests(TestCase):
+    def setUp(self):
+        self.text1 = get_fixture('gim_3.1.txt')
+
+    def test_autotag(self):
+        lines = edumed.tagger(self.text1)
+        for l in lines:
+            print "| %s" % l
diff --git a/apps/catalogue/tests/files/gim_3.1.txt b/apps/catalogue/tests/files/gim_3.1.txt
new file mode 100644 (file)
index 0000000..d37fd18
--- /dev/null
@@ -0,0 +1,158 @@
+I. Informacje
+Numer porządkowy: 3.1
+Dział: Język mediów
+Tytuł modułu: Podróże po hipertekście
+Poziom edukacyjny: gimnazjum
+Autorzy: Piotr Drzewiecki, Michał Wysocki, Urszula Dobrowolska
+
+Wybrana kompetencja z Katalogu: [uzupełnimy na końcu]
+Odniesienie do podstawy programowej: [uzupełnimy na końcu]
+Słowa kluczowe: hipertekst, nowe media
+Moduły powiązane: [uzupełnimy na końcu]
+
+Cele operacyjne
+Uczestnik:
+- rozumie różnice między hipertekstem, a tekstem tradycyjnym;
+- dostrzega logiczne powiązania pomiędzy tekstami tworzącymi hipertekst; 
+- pamięta, że o efektywnym korzystaniu z hipertekstu decyduje precyzyjne określenie potrzeby informacyjnej; 
+- wie, w jaki sposób zaplanować strukturę tworzonego przez siebie hipertekstu.
+
+Wykorzystane metody
+- wykład
+- dyskusja
+- praca w grupach
+
+Pomoce
+*xero
+*tablica
+*kreda lub marker
+*papier dużego formatu
+*przybory do pisania
+*taśma klejąca lub klej
+
+
+II. Wiedza w pigułce
+
+Żyjemy w świecie tekstów. Gdziekolwiek nie zwrócimy naszej uwagi, coś do nas „przemawia”. 
+Niegdyś możliwości przekazywania treści były ograniczone. By wyrazić zamierzony sens posługiwano się przede wszystkim zapisem. Stąd tradycyjne rozumienie „tekstu” jako czegoś zapisanego czy wydrukowanego. 
+Rozwój technologii sprawił, że dziś możemy na szeroką skalę komunikować się również za pośrednictwem radia, telewizji czy internetu. Zdajemy sobie sprawę, że „przemawia” do nas nie tylko pismo, lecz także film, obraz czy dźwięk. Każde z mediów wytworzyło specyficzny język. Mówi się np. o języku filmu. Za jego pośrednictwem można np. tworzyć swoistą atmosferę czy wyrażać emocje bez użycia słów. 
+Aby odróżnić rozmaitość współczesnych tekstów od tych dawnych, tylko drukowanych, powstało określenie „tekstu kultury”. Tekstem kultury jest każdy wytwór człowieka, który jest uporządkowany wedle określonych reguł.  Tekstami kultury są nie tylko dzieła sztuki (obrazy, filmy czy książki), lecz także np. ubrania.
+Internet zajmuje szczególne miejsce pośród dzisiejszych mediów. Dzieje się tak nie tylko dlatego, że każdy może być w nim twórcą i na forum wyrażać swoje myśli. Także dlatego, że każdy sam kieruje w nim swą wędrówką. Nie pozwalają nam na to  klasyczne radio czy telewizja: możemy odbierać określone programy tylko wtedy, gdy są akurat emitowane. W internecie mamy dostęp do wszystkich informacji, gdy tylko sobie tego zażyczymy. To odbiorca określa, jaką drogą podąży w wyborze oglądanych stron, jakie ich elementy pominie, na który link kliknie. Dzięki temu współtworzy otrzymywany przekaz.
+Swobodę w tworzeniu przekazu, jaką daje internet, oddaje pojęcie hipertekstu. Hipertekst to zbiór tekstów połączonych ze sobą odniesieniami. Odbiór hipertekstu można porównać do swobodnej przechadzki po budynku o niezliczonej ilości pokoi. Sam decydujesz, do jakiego pokoju zajrzysz, ile czasu w nim spędzisz i jak dokładnie przyjrzysz się obecnym tam przedmiotom. Odczytywanie tradycyjnego tekstu zaś byłoby podobne do zaplanowanej odgórnie wycieczki z przewodnikiem, który zaprowadzi zwiedzającego tylko do niektórych pomieszczeń, w odpowiedniej kolejności. 
+
+III. Pomysł na lekcję
+
+Opis zawartości 
+Hipertekst to coś więcej niż tylko zbiór odnośników do innych dokumentów. Jego zastosowanie, np. w Wikipedii, sprawia, że czytelnik samodzielnie może decydować o tym, które wątki pominąć, a które interesują go najbardziej, natychmiast zdobywając informacje na ich temat. Za sprawą hipertekstu każdy artykuł staje się swego rodzaju labiryntem, w którym każdy znajduje własną ścieżkę. 
+
+Przebieg zajęć
+1. Omów zagadnienie hipertekstu, korzystając z fragmentów podręcznika.
+Zwróć szczególną uwagę na najistotniejsze cechy hipertekstu:
+- nieliniowość – nie musimy za każdym razem czytać całości tekstu, a tylko interesujące nas fragmenty;
+- elastyczność – samodzielnie wybieramy te wątki, które chcemy rozwinąć; 
+- zwiększenie roli czytelnika – ten sam hipertekst można przeczytać na wiele różnych sposobów w zależności od zainteresowań i potrzeb poszczególnych osób.
+Zasygnalizuj uczestnikom zajęć, że praca z hipertekstem jest tym bardziej wymagająca – ze względu na wiele hiperłączy i łatwość pozyskiwania kolejnych informacji, powinniśmy precyzyjnie określić nasze potrzeby informacyjne tak, by móc wybrać te tematy, które rzeczywiście nas interesują. W przeciwnym razie łatwo o dekoncentrację i mimowolne podążanie za kolejnymi odnośnikami na zasadzie luźnej gry skojarzeń.
+Fakultatywnie możesz skorzystać z prezentacji  „Hipertekst_prezentacja”
+Jako pytania pomocnicze rozważ:
+- czy za każdym razem musimy przeczytać cały artykuł na Wikipedii, by dotrzeć do interesujących nas zagadnień?
+- czym różni się sposób pozyskiwania informacji z Wikipedii od tradycyjnej encyklopedii?
+- w jaki sposób hiperłącza stosowane np. w Wikipedii ułatwiają nam dostęp do informacji?
+pomoce: -
+forma pracy: wykład
+czas: 5 minut
+
+2. Podziel grupę na cztery zespoły. Dwa zespoły powinny otrzymać wydruk karty pracy „Hipertekst__A”, dwa zespoły wydruk karty pracy „Hipertekst__B”.
+Poproś zespoły o przygotowanie na papierze dużego formatu prezentacji/plakatu w formie krótkiego tekstu:
+            zespoły rozwiązujące zadanie A – w jaki sposób można przygotować jajka na śniadanie?
+            zespoły rozwiązujące zadanie B – jaka pogoda panuje w Polsce w październiku?
+
+Zwróć uwagę uczestników zajęć na konieczność wykorzystania przygotowanych hiperłączy – powinny zostać wkomponowane w powstające opisy. Dodatkowo każdy zespół powinien przygotować jedno własne hiperłącze i samodzielnie je opisać. 
+  
+pomoce: papier dużego formatu, karta pracy „Hipertekst__A”, karta pracy „Hipertekst__B”, taśma klejąca lub klej
+forma pracy: praca w grupach
+czas: 15 minut
+3. Poproś kolejne grupy o prezentacje. Pozostałe zespoły nakłoń do zadawania pytań i dzielenia się komentarzami.
+Zadaj pytania: czy zespoły opracowujące to samo zadanie przygotowały identyczne teksty? Czy hiperłącza zostały ułożone w ten sam sposób?
+Celem zadania jest uświadomienie uczestnikom zajęć, że hiperteksty mogą zostać skonstruowane na wiele różnych sposobów. Ich podstawowym celem jest umożliwienie odbiorcom elastycznego pozyskiwania wiedzy. 
+pomoce: papier dużego formatu
+forma pracy: prezentacja, dyskusja
+czas: 20 minut
+4. Zadaj pytanie: na czym polega największa zaleta informacji prezentowanych za pomocą hipertekstu?
+Jako pytania pomocnicze rozważ:
+- jak długie musiałby być tradycyjny tekst, aby zawrzeć wszystkie informacje, które zostały przedstawione w prezentacjach opracowywanych w czasie zajęć?
+- która forma – hipertekst czy tekst tradycyjny – jest bardziej użyteczna dla czytelnika?
+- dlaczego wygodniej korzystać z Wikipedii niż tradycyjnej encyklopedii?
+pomoce: -
+forma pracy: dyskusja
+czas: 5 minut
+Opcje dodatkowe
+Zajęcia mogą zostać rozbudowane w punkcie III.2 i III.3 o przygotowanie kolejnych odnośników do hiperłączy zawartych w karcie pracy. Poproś uczestników zajęć o podkreślenie na poszczególnych wydrukach słów, które mogą zostać wyjaśnione za pomocą kolejnych odniesień. Zasugeruj uczestnikom przygotowanie 2-3 hiperłączy wraz z krótkim wyjaśnieniem pojęć.
+
+IV. Materiały 
+karta pracy "Podróże po hipertekście"
+
+V. Zadania sprawdzające
+1.   Dobierz właściwe hiperłącza do oznaczonych fragmentów poniższego tekstu:
+
+„Sukces - działanie na najwyższym poziomie możliwości jednostki (HIPERŁĄCZE_1), w kierunku spełnienia jej marzeń (HIPERŁĄCZE_2) i pragnień przy jednoczesnym zachowaniu równowagi pomiędzy wszystkimi płaszczyznami życia.
+Innymi słowy sukces jest to stan zamierzony, zrealizowany w przeciągu czasu. Do zdefiniowania sukcesu określa się wartości, które są jego wyznacznikiem. Dla każdego człowieka sukces jest widziany w inny sposób. Ważną rolę odgrywa tutaj hierarchia wartości  i doświadczenia wyznawane przez każdego z osobna. Miarą sukcesu mogą być: zrealizowane plany, szczęście (HIPERŁĄCZE_3), zdrowie (HIPERŁĄCZE_4), własność materialna itp.
+Człowiek osiąga sukces, wtedy gdy spełniają się wszystkie jego oczekiwania.”
+Sukces, Wikipedia.pl, CC BY-SA, http://pl.wikipedia.org/wiki/Sukces_%28stan%29, dostęp 1.10.2012.
+
+Hiperłącza:
+a. Marzenia dzienne - fantazje, myśli i wyobrażenia występujące w czasie pełnej świadomości.
+Marzenia dzienne mogą dotyczyć przeszłości, teraźniejszości lub przyszłości. Dotyczą różnych tematów, wyróżnia się m.in. marzenia dotyczące sukcesu, czynów bohaterskich, relacji z innymi ludźmi, religijne itp. Dotyczą bardziej złożonych konstruktów myślowych, bądź bardzo prozaicznych.
+Badania prowadzone przez Jerome'a Singera (Marzenia dzienne) wykazały pewne różnice dotyczące treści marzeń dziennych w zależności od płci, wieku, pozycji społecznej itp. Wpływają one znacząco na funkcjonowanie człowieka. Freud przypisywał im rolę kompensacyjną. Mogą występować jako mechanizm obronny - uciekanie w świat fantazji, eskapizm.
+Marzenia dzienne, Wikipedia.pl, CC BY-SA, http://pl.wikipedia.org/wiki/Marzenia_dzienne, dostęp 1.10.2012.
+b. W konstytucji z 1946 roku Światowa Organizacja Zdrowia (WHO) określiła zdrowie jako „stan pełnego, dobrego samopoczucia fizycznego, psychicznego i społecznego, a nie wyłącznie brak choroby lub niedomagania (ułomności)”. W ostatnich latach definicja ta została uzupełniona o sprawność do „prowadzenia produktywnego życia społecznego i ekonomicznego” a także wymiar duchowy.
+Zdrowie, Wikipedia.pl, CC BY-SA, http://pl.wikipedia.org/wiki/Zdrowie, dostęp 1.10.2012.
+c. Szczęście jest emocją, spowodowaną doświadczeniami ocenianymi przez podmiot jako pozytywne. Psychologia wydziela w pojęciu szczęście rozbawienie i zadowolenie.
+rozważaniach o jego naturze szczęście najczęściej określane jest w dwu aspektach:
+*mieć szczęście oznacza: 
+*sprzyjający zbieg, splot okoliczności;
+*pomyślny los, fortuna, dola, traf, przypadek;
+*powodzenie w realizacji celów życiowych, korzystny bilans doświadczeń życiowych
+*odczuwać szczęście oznacza: 
+*(chwilowe) odczucie bezgranicznej radości, przyjemności, euforii,zadowolenia, upojenia,
+*(trwałe) zadowolenie z życia połączone z pogodą ducha i optymizmem; ocena własnego życia jako udanego, wartościowego, sensownego. 
+Trzeba tu zauważyć, że są to aspekty rozdzielne.
+Według niektórych neurologów, szczęście pozostaje w ścisłym związku z poziomem serotoniny w synapsach jąder szwu, a także dopaminy w jądrze półleżącym oraz endorfin. Jako dowód przytacza się odczuwane szczęścia pod wpływem substancji dopaminergicznych, serotoninergicznych oraz agonistów receptora opioidowego.
+Szczęście, Wikipedia.pl, CC BY-SA, http://pl.wikipedia.org/wiki/Szcz%C4%99%C5%9Bcie, dostęp 1.10.2012.
+d. Osoba (πρόσωπον /prosopon/, łac. persona) - pierwotnie, zarówno po grecku, jak i po łacinie słowo to oznaczało "maskę", którą zakładali aktorzy w teatrze starożytnym. Następnie zaczęto je używać do roli, jaką jednostka odgrywa w dramacie życia. Podmiot o rozumnej naturze. Może nim być człowiek, a także Bóg, rozumiany jako byt wyróżniający się najdoskonalszą formą istnienia. We współczesnej filozofii pojęcie kluczowe dla chrześcijańskiego i niechrześcijańskiego personalizmu, mającego źródła w chrześcijańskim (zwłaszcza tomistycznym) rozumieniu człowieka jako bytu odrębnego od świata rzeczy, przyrody, w tym także zwierząt.
+Osoba, Wikipedia.pl, CC BY-SA, http://pl.wikipedia.org/wiki/Osoba, dostęp 1.10.2012.
+
+ROZWIĄZANIE:
+1 – d
+2 – a
+3 – c
+4 – b
+
+VI. Słowniczek 
+http://pad.nowoczesnapolska.org.pl/p/slowniczek
+Tekst kultury - każdy  wytwór kulturalnej działalności człowieka, stanowiący uporządkowaną  całość wedle określonych reguł np. dzieło sztuki (obraz, film, książka),  ale też ubiór, przyjęty wzór zachowań społecznych
+Hipertekst  - nielinearny sposób organizacji tekstów lub ich fragmentów połączonych  odsyłaczami, umozliwiający czytelnikowi bardziej swobodną lekturę.  Przykładem hipertekstu jest strona www połączona z innymi za pomocą  linków
+
+VII. Czytelnia 
+M. Filiciak, A. Tarkowski, Alfabet nowej kultury: H jak hipertekst,  http://www.dwutygodnik.com/artykul/323 (dostęp 16.09.2012)
+M. Pisarski, Hipertekst - punkt węzłowy, http://www.techsty.art.pl/hipertekst.htm
+
+VIII. Ewaluacja 
+Czy po przeprowadzeniu zajęć ich uczestnicy:
+- rozumieją różnicę pomiędzy hipertekstem a tekstem tradycyjnym?
+- potrafią wskazać najistotniejsze zalety prezentowania informacji w formie hipertekstu?
+- wiedzą, w jaki sposób zaplanować strukturę hipertekstu?
+- rozumieją, że o efektywnym wykorzystaniu hipertekstu decyduje precyzyjne określenie potrzeby informacyjnej (--> rozważ realizację modułu 1.1 Jak planować pracę z informacją?)
+
index 43150e4..31e86b4 100644 (file)
@@ -49,6 +49,7 @@ COMPRESS_JS = {
                 # wiki scripts
                 'js/wiki/wikiapi.js',
                 'js/wiki/xslt.js',
+                'js/wiki/xml-tools.js',
 
                 # base UI
                 'js/wiki/base.js',
index 0d43611..e2d70e3 100644 (file)
@@ -675,3 +675,53 @@ div[x-node] > .uwaga {
     border:0;
     color: black;
 } 
+
+
+.htmlview .lista[data-wlf-typ="alfa"] 
+{
+    list-style-type: lower-alpha;
+}
+
+.htmlview .lista[data-wlf-typ="slowniczek"] 
+{
+    list-style-type: none;
+}
+
+.htmlview span.punkt {
+    display: list-item;
+    list-style-position: inside;
+}
+
+.htmlview span.aktywnosc span.opis {
+    font-style: italic;
+}
+.htmlview span.aktywnosc span.wskazowki {
+    font-style: italic;
+    color: #202020;
+}
+
+.htmlview .pomoce, .htmlview .czas, .htmlview .forma {
+    display: block;
+}
+.htmlview .pomoce:before { content: "Pomoce: "; }
+.htmlview .czas:before { content: "Czas: "; }
+.htmlview .czas:after { content: " min"; }
+.htmlview .forma:before { content: "Forma:"; }
+
+.htmlview .aktywnosc {
+    display: block;
+    border: 1px dotted #996600;
+}
+
+.htmlview .definiendum {
+    font-weight: bold;
+    display: block;
+}
+
+.htmlview .definiens {
+    padding-left: 50px;
+}
+
+.htmlview .link {
+    color: #990066;
+}
diff --git a/redakcja/static/js/wiki/xml-tools.coffee b/redakcja/static/js/wiki/xml-tools.coffee
new file mode 100644 (file)
index 0000000..66d04b3
--- /dev/null
@@ -0,0 +1,48 @@
+
+window.walk = (node, handler) ->
+  if handler.text
+    textHandler = handler.text
+  else
+    textHandler = handler
+
+  switch node.nodeType
+    when 1, 9, 11
+      child = node.firstChild
+      while child
+        nxt = child.nextSibling
+        walk(child, handler)
+        child = nxt
+    when 3
+      textHandler node
+
+
+window.wrapInTag = (regex, tagName) ->
+  fun = (node) ->
+    matches = []
+    while m = regex.exec(node.nodeValue)
+      matches.push [regex.lastIndex, m[0]]
+
+    matches.reverse()
+
+    for m in matches
+      to = m[0]
+      frm = m[0] - m[1].length
+
+      node_rest = node.splitText(to)
+      alien = node.splitText(frm)
+      wrapper = node.ownerDocument.createElement tagName
+      node.parentNode.insertBefore wrapper, node_rest
+      wrapper.appendChild alien
+    node
+
+
+# window._test_xml_tools = ->
+#   p = new DOMParser()
+#   dom = p.parseFromString("<a><b>łuków</b><c>jakaś jeszcze ~</c></a>", 'text/xml')
+
+#   walk(dom.firstChild, wrapInTag(ALIEN_REGEX, 'alien'))
+#   dom
+
+
+# window.ALIEN_REGEX = /[^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\s<>«»\\*_!,:;?&%."'=#()\/-]+/g
+# " just for syntax coloring.
diff --git a/redakcja/static/js/wiki/xml-tools.js b/redakcja/static/js/wiki/xml-tools.js
new file mode 100644 (file)
index 0000000..db5da83
--- /dev/null
@@ -0,0 +1,51 @@
+(function() {
+
+  window.walk = function(node, handler) {
+    var child, nxt, textHandler, _results;
+    if (handler.text) {
+      textHandler = handler.text;
+    } else {
+      textHandler = handler;
+    }
+    switch (node.nodeType) {
+      case 1:
+      case 9:
+      case 11:
+        child = node.firstChild;
+        _results = [];
+        while (child) {
+          nxt = child.nextSibling;
+          walk(child, handler);
+          _results.push(child = nxt);
+        }
+        return _results;
+        break;
+      case 3:
+        return textHandler(node);
+    }
+  };
+
+  window.wrapInTag = function(regex, tagName) {
+    var fun;
+    return fun = function(node) {
+      var alien, frm, m, matches, node_rest, to, wrapper, _i, _len;
+      matches = [];
+      while (m = regex.exec(node.nodeValue)) {
+        matches.push([regex.lastIndex, m[0]]);
+      }
+      matches.reverse();
+      for (_i = 0, _len = matches.length; _i < _len; _i++) {
+        m = matches[_i];
+        to = m[0];
+        frm = m[0] - m[1].length;
+        node_rest = node.splitText(to);
+        alien = node.splitText(frm);
+        wrapper = node.ownerDocument.createElement(tagName);
+        node.parentNode.insertBefore(wrapper, node_rest);
+        wrapper.appendChild(alien);
+      }
+      return node;
+    };
+  };
+
+}).call(this);
index 1327fc6..618dcd0 100644 (file)
@@ -59,12 +59,15 @@ function withThemes(code_block, onError)
 
 
 function xml2html(options) {
+    ALIEN_REGEX = /([^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\s<>«»\\*_!,:;?&%."'=#()\/-]+)/g;
+
     withStylesheets(function() {
         var xml = options.xml.replace(/\/(\s+)/g, '<br />$1');
-        xml = xml.replace(/([^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\s<>«»\\*_!,:;?&%."'=#()\/-]+)/g, '<alien>$1</alien>');
+//        xml = xml.replace(/([^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ\s<>«»\\*_!,:;?&%."'=#()\/-]+)/g, '<alien>$1</alien>');
         var parser = new DOMParser();
         var serializer = new XMLSerializer();
         var doc = parser.parseFromString(xml, 'text/xml');
+       walk(doc.firstChild, wrapInTag(ALIEN_REGEX, 'alien'))
         var error = $('parsererror', doc);
 
         if (error.length == 0) {
index 4f64291..afb3366 100644 (file)
         </span>
     </xsl:template>
 
+    <xsl:template match="lista">
+      <xsl:variable name="listtag">
+       <xsl:choose>
+         <xsl:when test="@typ='num' or @typ='alfa'">ol</xsl:when>
+         <xsl:when test="@typ='punkt' or @typ='slowniczek' or @typ='czytelnia'">ul</xsl:when>
+         <xsl:otherwise>ul</xsl:otherwise>
+       </xsl:choose>
+      </xsl:variable>
+      <xsl:element name="{$listtag}">
+        <xsl:call-template name="standard-attributes" />
+        <xsl:apply-templates select="child::node()">
+          <xsl:with-param name="mixed" select="true()" />
+        </xsl:apply-templates> 
+      </xsl:element>
+    </xsl:template>
+
+
+    <xsl:template match="punkt[../@typ='slowniczek']">
+      <dl x-node="punkt" class="punkt">
+            <xsl:call-template name="standard-attributes" />
+            <xsl:apply-templates select="child::node()">
+                <xsl:with-param name="mixed" select="true()" />
+            </xsl:apply-templates>     
+      </dl>
+    </xsl:template>
+
+    <xsl:template match="punkt[../@typ!='slowniczek']">
+      <li x-editable="true" x-node="punkt" class="punkt">
+            <xsl:call-template name="standard-attributes" />
+            <xsl:apply-templates select="child::node()">
+                <xsl:with-param name="mixed" select="true()" />
+            </xsl:apply-templates>     
+      </li>
+    </xsl:template>
+
+
+    <xsl:template match="definiendum">
+      <dt x-editable="true" x-node="definiendum" class="definiendum">
+            <xsl:call-template name="standard-attributes" />
+            <xsl:apply-templates select="child::node()">
+                <xsl:with-param name="mixed" select="true()" />
+            </xsl:apply-templates>
+      </dt>
+    </xsl:template>
+
+    <xsl:template match="definiens">
+      <dd x-editable="true" x-node="definiendum" class="definiendum">
+            <xsl:call-template name="standard-attributes" />
+            <xsl:apply-templates select="child::node()">
+                <xsl:with-param name="mixed" select="true()" />
+            </xsl:apply-templates>
+      </dd>
+    </xsl:template>
 
     <!--
         ****************
 
     <xsl:template match="*">
         <span class="unknown-tag" x-node="{name()}">
+            <xsl:call-template name="standard-attributes" />
             <xsl:apply-templates select="child::node()">
                 <xsl:with-param name="mixed" select="true()" />
             </xsl:apply-templates>        
index 370f6a9..4c3aafd 100644 (file)
@@ -20,3 +20,6 @@ django-kombu
 
 # migrations
 south>=0.6
+
+# etherpad lite api
+-e git+git://github.com/devjones/PyEtherpadLite.git#egg=PyEtherpadLite
diff --git a/scripts/make-tags b/scripts/make-tags
new file mode 100755 (executable)
index 0000000..0f2d26c
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+ROOT=$(git rev-parse --show-toplevel)
+
+find $ROOT -name '*.py' | xargs etags -o ${ROOT}/TAGS
+if [ -n "$VIRTUAL_ENV" ]; then
+  find ${VIRTUAL_ENV}/lib -name '*.py' |xargs etags -a -o ${ROOT}/TAGS
+else
+    echo "No Virtual env enabled, will not add it to TAGS"
+fi
+
+find $ROOT/redakcja/static/css -name '*.css' |xargs etags -a -o ${ROOT}/TAGS
+find $ROOT/redakcja/static/js -name '*.js' |xargs etags -a -o ${ROOT}/TAGS