Jest coś takiego jak Katalog Polskiej Muzyki Akordeonowej (KPMA), który powstaje przy wykorzystaniu cutting edge technologii: jest redagowany jako plik XML w edytorze strukturalnym.
Na początku KPMA był plikiem TeXowym, później (2002 r.) został przerobiony na XML
i odtąd był redagowany w Emacsie,
w trybie psgml.
Struktura dokumentu była/jest opisana za pomocą stosownego DTD, które psgml
potrafi
interpretować.
W tzw. międzyczasie
tryb psgml
stał się obsolete na rzecz
nXML
.
Można wprawdzie dalej używać psgml
(działa bez problemów) i być może
dałoby się skonfigurować Emacsa, w taki sposób iż
dokumenty KPMA byłby redagowane w psgml
a inne dokumenty XML w trybie nXML
, ale koniec końców zdecydowałem się
na zmianę.
Aby zmienić DTD na RNC, wystarczy posłużyć się trang
iem:
trang -I dtd -O rnc lkompc.dtd lkompc.rnc
Teraz należy dodać schemat do konfiguracji nXML. Wystarczy w tym celu wczytać
plik Kompozycje.xml
a następnie wybrać XML→Set Schema→File. Wybrać plik lkompc.rnc
.
W katalogu z dokumentem XML (tj. Kompozycje.xml
w tym konkretnym przypadku) zostanie
zapisany plik schemas.xml
, zawierający:
<?xml version="1.0"?> <locatingRules xmlns="http://thaiopensource.com/ns/locating-rules/1.0"> <uri resource="Kompozycje.xml" uri="lkompc.rnc"/> </locatingRules>
Od tego momentu Kompozycje.xml
będzie edytowany z nastawami
schematu określonymi w lkompc.rnc
.
Plik XML wygląda tak:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE lista.kompozycji SYSTEM "lkompc.dtd" > <lista.kompozycji> <kompozycja typ="i.orkiestro"> <tytul>Atlantyda I na orkiestrę symfoniczną</tytul> <xsklad>4 akordeony w orkiestrze</xsklad> <autor> <nazwisko>Augustyn</nazwisko> <imie>Rafał</imie> </autor> <rok>1979</rok> <sklad>4 acc</sklad> <wydawca>manus</wydawca> </kompozycja> ... <kompozycja typ="solo"> <tytul>Rapsodia</tytul> <xsklad>akordeon solo</xsklad> <autor> <nazwisko>Krzanowski</nazwisko> <imie>Andrzej</imie> </autor> <autor> <nazwisko>Krzanowska</nazwisko> <imie>Grażyna</imie> </autor> <rok>1983</rok> <wydawca>PWM</wydawca> <nagranie>KM</nagranie> </kompozycja> ... </lista.kompozycji>
a ma wyglądać tak:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE lista.kompozycji SYSTEM "lkompc.dtd" > <lista.kompozycji> <kompozytor id='Augustyn.R'><!-- *** Augustyn:Rafał# --> <kompozycja typ="i.orkiestro"> <tytul>Atlantyda I na orkiestrę symfoniczną</tytul> <xsklad>4 akordeony w orkiestrze</xsklad> <rok>1979</rok> <sklad>4 acc</sklad> <wydawca>manus</wydawca> </kompozycja> <kompozycja typ="i.orkiestro"> <tytul>Atlantyda II na wielką orkiestrę i chór</tytul> <xsklad>4 akordeony w orkiestrze</xsklad> <rok>1983</rok> <sklad>4 acc</sklad> <wydawca>manus</wydawca> <nagranie>LP</nagranie> </kompozycja> </kompozytor> ... <kompozytor id='Krzanowski.A#Krzanowska.G'><!-- *** Krzanowski:Andrzej#Krzanowska:Grażyna# --> <kompozycja typ="solo"> <tytul>Rapsodia</tytul> <xsklad>akordeon solo</xsklad> <rok>1983</rok> <wydawca>PWM</wydawca> <nagranie>KM</nagranie> </kompozycja> ... </lista.kompozycji>
To znaczy, że
z elementu kompozycja
mają zniknąć elementy autor
.
Wszystkie kompozycje tego samego kompozytora mają być elementami-dziećmi
elementu kompozytor
.
Element kompozytor
ma identyfikować
kompozytora za pomocą atrybutu id
, którego wartość jest wyznaczana
(w przypadku gdy dzieło jest ma jednego autora) jako:
nazwisko.inicjał
W przypadku gdy kompozycja jest dziełem zbiorowym, identyfikator kompozytora zbiorowego ma mieć postać:
nazwisko.inicjał#nazwisko.inicjał nazwisko.inicjał#nazwisko.inicjał#nazwisko.inicjał ...
Powyższe realizuje taki oto skrypt:
#!/usr/bin/perl use XML::DOM; binmode(STDOUT, ":utf8"); my $file2parse = $ARGV[0]; my $parser = new XML::DOM::Parser; my $doc = $parser->parsefile ($file2parse); for my $kompozycja ( $doc->getElementsByTagName ("kompozycja") ) { my $author_id = ''; ## przeglądamy kolejne elementy autor: for my $autor ($kompozycja->getElementsByTagName("autor")) { $im = ($autor->getElementsByTagName("imie"))[0]->toString(); $nz = ($autor->getElementsByTagName("nazwisko"))[0]->toString(); $author_id .= "$nz:$im#"; ## autorów może być dużo stąd .= a nie = $author_id =~ s/<[^<>]+>//g; ## usuń tagi, zostaw sam tekst ##print STDERR "$author_id\n"; ## usuń element autor: $kompozycja->removeChild($autor); } ## Hash of Arrays, cf http://docstore.mik.ua/orelly/perl2/prog/ch09_02.htm push @{ $Kompozycje{ $author_id }}, $kompozycja->toString (); } ### Druk ############################################################ print "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"; print "<!DOCTYPE lista.kompozycji SYSTEM \"lkompc.dtd\" >\n"; print "<lista.kompozycji>\n"; for $autor (sort keys %Kompozycje ) { $autor_i = $autor; $autor_i =~ s/:([^#:])[^#:]+#/.\1#/g; # tylko inicjały chop($autor_i); print "\n\n\n\n<kompozytor id='$autor_i'><!-- *** $autor -->\n\n"; for $kompozycja ( @{ $Kompozycje{ "$autor" }} ) { print $kompozycja, "\n"; } print "\n</kompozytor>\n"; } print "</lista.kompozycji>\n"; ## koniec ###
Jeżeli się nie doda binmode
, to UTF jest malformed (Ah ten Perl.)
Podpowiedź znalazłem
tutaj.
Nawiasem mówiąc i w innym skrypcie:
s/<imie>([^<>])([^<>]+)<\/imie>/<inicjal>\1<\/inicjal>/gm;
Też zwraca malformed UTF-8 jeżeli np. imieniem jest Łukasz. A jak zaczyna się od A-Z to jest OK.
Zadanie polega na wydłubaniu z pliku GPX pierwszego i ostatniego elementu <time>, który jest dzieckiem elementu <trkpt>. (Nie może być po prostu <time>, bo element ten występuje także w innym kontekście.)
Coś takiego wymyśliłem:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:gpx="http://www.topografix.com/GPX/1/0"> <xsl:output method="text"/> <xsl:param name='Position' select="'First'"/> <xsl:template match="/" > <xsl:for-each select='//gpx:trkpt'> <xsl:if test="position()=1 and $Position='First'"> <xsl:value-of select='gpx:time'/> <xsl:text> </xsl:text> </xsl:if> <xsl:if test="position()=last() and $Position='Last'"> <xsl:value-of select='gpx:time'/> <xsl:text> </xsl:text> </xsl:if> </xsl:for-each> </xsl:template> </xsl:stylesheet>
W praniu się okazało, że jest jeden drobny, komplikujący życie feler-nie-feler.
Struktura plików GPX (w zależności od tego
czym są one generowane) jest identyfikowana albo
z przestrzenią nazw http://www.topografix.com/GPX/1/0
albo z http://www.topografix.com/GPX/1/1
. Praktycznie żadna różnica, ale szablon
przestaje działać. Aby uodpornić szablon na wersje formatu można zamienić:
<xsl:for-each select='//gpx:trkpt'>
na
<xsl:for-each select='//*[local-name()="trkpt"]'>
Teraz szablon `reaguje' na element <trkpt> z dowolnej przestrzeni nazw. Oczywiście reguła wyboru węzłów stała się teraz `mniej specyficzna', co w przypadku bardziej skomplikowanych transformacji może nie być najlepszym pomysłem, ale w przypadku tak prostego schematu jak GPX i banalnej transformacji, jak powyższa nie powinno być problemów.
Podobnie można zamienić:
<xsl:value-of select='gpx:time'/>
na:
<xsl:value-of select='*[local-name()="time"]'/>
Teraz, przykładowo uruchomienie xsltproc (opisany wyżej
szablon jest zawartością pliku gpxtimestamp.xsl
):
xsltproc --param Position '"First"' gpxtimestamp.xsl 20120107.xml xsltproc --param Position '"Last"' gpxtimestamp.xsl 20120107.xml
Wypisuje odpowiednio zawartość pierwszego i ostatniego elementu <time>.
Sprawdziłem dziś http://pinkaccordions.homelinux.org/wblog
za pomocą
xmllinta
i ponieważ się okazało, że są błędy postanowiłem skończyć z partyzantką.
Od dziś kilka stron będę weryfikował automatem, np. w taki sposób:
SGML_CATALOG_FILES=~/etc/xml/catalog xmllint --catalogs --noout --valid http://pinkaccordions.homelinux.org/
Jeżeli korzystamy z domyślnego /etc/xml/catalog
, to
podanie SGML_CATALOG_FILES
jest zbędne.
W tymże pliku ~/etc/xml/catalog
dodałem wpisy:
<uri name="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" uri="file:///home/tomek/etc/xml/dtd/xhtml/1.1/xhtml11.dtd" /> <uri name="http://www.w3.org/TR/ruby/xhtml-ruby-1.mod" uri="file:///home/tomek/etc/xml/dtd/xhtml/1.1/xhtml-ruby-1.mod" /> <rewriteSystem systemIdStartString="http://www.w3.org/TR/xhtml-modularization/DTD/" rewritePrefix="file:///home/tomek/etc/xml/dtd/xhtml/1.1/" /> <rewriteURI uriStartString="http://www.w3.org/TR/xhtml-modularization/DTD/" rewritePrefix="file:///home/tomek/etc/xml/dtd/xhtml/1.1/" />
Uwaga: pierwsze dwa wiersze nie wystarczą,
bo xhtml11.dtd
dołącza całą chmarę innych plików --
konkretnie 35 plików .mod
oraz 3 .ent
. Wszystkie te pliki skopiowałem do
/home/tomek/etc/xml/dtd/xhtml/1.1/
Teraz wywołuję xmllint
za pomocą prostego skryptu:
#!/bin/bash # Sprawdzanie poprawnosci wybranych stron na pinkaccordions.homelinux.org LOGFILE=~/Logs/WWW/xhtml-errors.log XMLCATALOG=~/etc/xml/catalog LINT=/usr/bin/xmllint TODAY=`date` for url in 'http://pinkaccordions.homelinux.org/wblog' 'http://pinkaccordions.homelinux.org/' ... do SGML_CATALOG_FILES=$XMLCATALOG $LINT --catalogs --noout --valid $url if [ "$?" -ne 0 ] ; then echo "Error found in $url [$TODAY]" >> $LOGFILE fi done
Ewentualnie można też tak:
#!/usr/bin/perl # Sprawdzanie poprawnosci za pomoca `W3C Markup Validation Service' use LWP::Simple; $LOGFILE='/home/tomek/Logs/WWW/xhtml-errors.log'; my $today = localtime; open LOG, ">>$LOGFILE"; my @URLs = ( 'http://validator.w3.org/check?uri=http%3A%2F%2Fpinkaccordions.homelinux.org%2Fwblog', 'http://validator.w3.org/check?uri=http%3A%2F%2Fpinkaccordions.homelinux.org', ... ); foreach $url (@URLs) { print STDERR "Sprawdzam: $url...\n"; $content = get("$url"); if ($content =~ /Information on validation.*Congratulations/m) { print STDERR "$url is OK\n" } else { print LOG "$today => errors found => $url\n" ; } } close (LOG);
W3C ma też API do serwisu walidacyjnego, ale skomplikowane to API jest...
Xmllint
ma użyteczną opcję, która pozwala
sprawdzić poprawność pliku .xml
względem zewnętrznego pliku DTD,
tj. plik nie musi zawierać deklaracji DOCTYPE
. Wygląda
jednak, że implementacja tegoż zawiera poważne błędy.
O tyle
sprawa jest dziwna, że w google na ten temat nic nie ma a błąd jest
tak oczywisty, że
powinno być. Przykładowe DTD (A.dtd
):
<!ELEMENT a (#PCDATA) > <!ATTLIST a b CDATA "Wartosc domyslna" > <!ENTITY e "Tresc encji" >
Przykładowy plik (A.xml
):
<?xml version="1.0" encoding='ISO-8859-2'?> <!-- <!DOCTYPE a SYSTEM "A.dtd"> --> <a>Przykład encji: &e;</a>
teraz uruchomienie
xmllint
z opcją dtdvalid
:
$ xmllint --dtdvalid A.dtd A.xml A.xml:3: parser error : Entity 'e' not defined
kończy się błędem...
Smród pewien powstał wokół tzw. Giganta z Redmond a zwłaszcza jego sztandarowego produktu pn. Word. W skrócie ogromnym -- podobno -- sędzia Leonard Davis (Tyler/Texas) zakazał od 12. października 2009 sprzedaży ww. Worda, co jest efektem naruszenia patentu #5787449 firmy i4i.
Ustalić cóż takiego konkretnie wymyśliła ta firma i4i nie jest proste. Wniosek patentowy jest dość abstrakcyjny, no i na dokładkę trzeba wiedzieć co zaimplementowano w Wordzie, żeby ustalić na ile pozew i4i jest uzasadniony -- przynajmniej w aspekcie prawnym. Poszukując szczegółów via Google rozpocząłem od tekstu konsekwencje pozwu patentowego trolla, w którym twierdzi się, że patent sprowadza się do ,,możliwości definiowania danych w składni Schematu XML, a następnie wykorzystywania tych danych w dokumentach Office''. Dalej autor ocenia, że ,,trudno o coś równie ogólnego i trywialnego, a patent taki nigdy nie powinien zostać przyznany''.
Na moje wszak oko to nie jest podstawa pozwu i4i -- szczegóły wkrótce.
Dopisane 16 sierpnia 2009: przejrzałem patent i poniżej zamieszczam kluczowe fragmenty z komentarzem.
It is an object of the present invention to provide an improved method of encoding a document. [...] in sharp contrast to the prior art the present invention is based on the practice of separating encoding conventions from the content of a document [...] The invention does not use embedded metacoding to differentiate the content of the document, but rather, the metacodes of the document are separated from the content and held in distinct storage in a structure called a metacode map, whereas document content is held in a mapped content area.
Ostatnie zdanie wydaje się dotyczyć istoty patentu. Nie jest to wcale ,,możliwości definiowania danych w składni Schematu XML''. Bzdura. Chodzi o przetwarzanie dokumentu XML (i nie tylko XML -- przypuszczam, że każdego dokumentu elektronicznego przetwarzanego w sposób opisany we zastrzeżeniach wniosku patentowego) w taki sposób, że treść i formatowanie są zapisywane w oddzielnych miejscach (held in distinct storage). Jak to ma działać jest objaśnione dalej:
A metacode map is a multiplicity of metacodes and their addresses associated with mapped content. An address is the place in the content at which the metacode is to exert its effect.
Łopatologicznie można to przedstawić na przykładzie poniżej. Po lewej stronie dokument XML/SGML a po prawej ten sam dokument bez znaczników (nazwanych metacodes we wniosku patentowym) -- są one przechowywane w innym pliku/miejscu i wstawiane wtedy kiedy jest to potrzebne.
<p>Pod źdźbłem <strong>spał</strong> Pod źdźbłem spał żółw śnięty żółw śnięty </p>
BTW w ten sposób dokument może być oznakowany za pomocą różnych systemów znaczników (przynajmniej teoretycznie, bo w praktyce jakoś trudno mi wyobrazić redagowanie takiego dokumentu), podczas gdy w ,,klasycznym'' XMLu treść+oznakowanie stanowi jak wiemy monolit.
Nie chce mi się dalej czytać, ale całe to invention sprowadza się zatem do ,,sprytnego'' redagowania dokumentów (w tym dokumentów XML/SGML), w którym znacznik nie jest wstawiany bezpośrednio do treści dokumentu ale jest umieszczany osobno wraz ze wskaźnikiem gdzie ma być wstawiony (the patent describes separating markup from the content stream by means of an map indexing the content).
Reasumując: 1) rzecz cała sprowadza się w dużym stopniu do interfejsu; 2) widocznie Word korzysta z takiego rozwiązania; 3) patent jest dobitnym przykładem czegoś co należało do stanu techniki (aka prior art) i jako takie nie powinno w ogóle być patentowalne, bo cóż jest bardziej trywialnego niż zbiór wskaźników do czegokolwiek.
PS: sugestie głoszone przez ignorantów na różnych forach jakoby W3C albo Apache Fundation są zagrożone patentem i4i nie są prawdziwe, bo patent dotyczy tylko pewnego sposobu manipulowania dokumentem strukturalnym a nie XMLa/SGMLa jako takiego.
Fragment pliku XML wygląda jakoś tak:
<month no="1"> <item data="2009/01/03" dist="20" exdist="18.60" /> <item data="2009/01/25" dist="50" exdist="49.10" /> <month no="2"> ... </month>
Wszystkie item
pomiędzy month
trzeba zsumować aż do zadanego
numeru miesiąca (włącznie),
który jest n.b. wartością atrybutu no
. Poniższy szablon
wykona ww. zadanie:
<xsl:template name='drukuj-do-mc'> <xsl:param name='mc'/> <xsl:text> [od początku roku: </xsl:text> <xsl:value-of select="sum(//item[not(../@no > $mc)]/@dist)"/> <xsl:text> km] </xsl:text> </xsl:template>
Wyróżniony wiersz zawiera kluczowe przekształcenie XPath:
//item[not(../@no > $mc)]/@dist
, co można zinterpretować następująco:
wszystkie atrybuty dist
przyczepione do
elementu item
, z całego dokumentu, ale pod warunkiem, że wartość atrybutu
no
elementu nadrzędnego jest nie większa niż $mc
.
W nazwiązaniu do poprzedniego wpisu. W dokumencie o XPath pojawia się taki oto fragment:
[predykat]*]]>
Co ma oznaczać, że wyrażenie ścieżkowe XPath składa się z osi
i testu oraz opcjonalnego predykatu, który może być powtórzony
wielokrotnie (stąd *
).
Teraz standardowo szablony
XSL Docbook (aka XSLDB) zamieniają ww. fragment XML na coś takiego:
oś::test[[predykat]*]
Niezręczność polega na tym, że nawiasy kwadratowe raz są używane do
oznaczania części opcjonalnej a raz oznaczają, że należy je wstawić
literalnie. W XSLDB znaki wstawiane wokół
elementu optional
są sparametryzowane -- są to mianowicie:
arg.choice.opt.open.str
oraz
arg.choice.opt.close.str
.
Zamiast `[' i `]' zdecydowałem
się na U+27E8
(Mathematical left angle bracket) oraz U+27E9
(Mathematical right angle bracket) Teraz próba uruchomienia:
xsltproc --stringparam arg.choice.opt.open.str "⟨" \ --stringparam arg.choice.opt.close.str" select="⟩" ...
Kończy się błędem basha... Ciekawe czemu? Można podać ww. znaki binarnie -- wtedy wszystko działa. Ale UTF-8 w Makefile? Miałem opory, dodałem więc do arkusza uruchamiającego transformację XML → XHTML:
<xsl:param name="arg.choice.opt.open.str" select="'⟨'" /> <xsl:param name="arg.choice.opt.close.str" select="'⟩'" />
Teraz omawiany fragment wygląda mniej dwuznacznie:
oś::test⟨[predykat]*⟩
BTW w trybie nxml wpisanie ⟨
powoduje, że automagicznie
kształt znaku pojawia się za średnikiem (ale nie jest wstawiany do tekstu --
po prostu jest to podpowiedź Emacsa, jak wygląda kształt znaku.)
Dodatkowo po
najechaniu myszą na encję wyświetlana jest nazwa znaku (w oknie podpowiedzi zwanym tooltip).
Te ułatwienia są fajne w środowisku jednobajtowym--a ja póki co
używam jako domyślnego kodowania ISO-8859-2.
Przy okazji użyteczne zestawienie
znaków Unicode -- odpowiedników różnych TeXowych symboli matematycznych. Wystarczy zaznaczyć myszą
i wkleić do Emacsa a następnie C-x =
(what-cursor-position
)
wyświetli co zacz, w tym numer znaku.
Można używać nxml-mode do tworzenia schematów XSD. To odkrycie jest wynikiem szukania edytora/IDE ułatwiającego projektowanie Schemy. Oczywiście jest XMLSpy ale tylko dla MSW. Jest też jakiś moduł do netBeans, którego nie próbowałem... Niezbędny dla działania nxml-mode schemat do schemy w formacie RNG/RNC jest dostępny ze strony www.relaxng.org. W związku z tym odkryciem zmieniłem konfigurację emacsa:
(load "rng-auto.el") ;; http://www.emacswiki.org/emacs-en/NxmlMode (add-to-list 'auto-mode-alist (cons (concat "\\." (regexp-opt '("xml" "xsd" "rng" "xslt" "xsl" "svg" "rss") t) "\\'") 'nxml-mode))
Należy teraz ,,zarejestrować'' schemat do schemy
w pliku konfiguracyjnym
pakietu nxml-mode (schema/schemas.xml
):
<uri pattern="*.xsd" typeId="XML Schema"/> <namespace ns="http://www.w3.org/2001/XMLSchema" typeId="XML Schema"/> <typeId id="XML Schema" uri="xmlschema.rnc"/>
Oryginalny schemat powoduje błąd: regular expression too big in xmlschema.rnc.
Na tej
stronie jest to dokładnie opisane i jest udostępniony plik .diff
,
tyle że nie byłem
w stanie go zastosować uruchamiając patch
. Poprawiłem plik ręcznie.
Nie ma 100% gwarancji, że poprawka jest OK, ale dla kilku schematów
nxml-mode działał poprawnie, więc szanse są, że tak jest.
Poprawiony plik .rnc
jest tutaj
Teraz wszystkie pliki XML (w tym szablony XSLT oraz schematy XSD) mogę edytować w trybie nxml (dowód w postaci zrzutu ekranu obok).