Guide:
Arkeologisk XSLT : Frem fra glemselen (del 2)
I del 2 fortsetter emnekartspesialist Alexander Johannesen overhalingen av gammel XSLT-kode under overskriften "Kunsten å bygge bro over dårlig design".
Side 1: - XML, episode 4

La oss komme tilbake til vårt opprinnelige problem. Jeg fiklet med koden i et par minutter eller så (utviklingstid er, som vi alle vet, relativ). Begge (eller snarere, alle fire!) filene var vedlikeholdt for hånd, så jeg bestemte meg for ikke bare å skrive om hele XSLT-en men også restrukturere XML-en også. Tro meg, jeg handlet i nødverge!
- XML, episode 4 : Et nytt håp
Først endret jeg XML strukturen:
<guides>
<heading>Guides</heading>
<description>[snip]</description>
Borte er alle elementer med store bokstaver. Dette er nødvendigvis ikke noe stort poeng, men det kan ofte være at du må gjenskape semantiske likheter på tvers av plattformer, og plutselig har du Java eller C# klassen 'FastBeaver' og instanser som 'reallyFastBeaver'. Du veit aldri. Deretter:
<!-- Our subjects; choose a different label if the presentation needs to look different -->
<index-presentation>
<subject name="Maps">Maps</subject>
<subject name="Manuscripts">Manuscripts</subject>
<format name="Bibliography">Bibliographies</format>
<format name="Collection Guide">Collection Guides</format>
<format name="Discover Guide">Discover Guides</format>
</index-presentation>
Ok, jeg innrømmer det: Jeg kunne ha puttet presentasjonsrelaterte elementer i en annen fil, men for enkelhets skyld (og fordi de er semantisk klarert og klart separert) puttet jeg de inn i toppen av filen. Så jeg er en pragmatist. Uff da.
Deretter konverterte jeg alle data inn i en semantisk rikere og enklere struktur, og uten antyding til grupperinger;
<record>
<title>Aerial Photographs</title>
<description>[snip]</description>
<url>../../map/aerialphoto.html</url>
<subject>Maps</subject>
<format>Collection Guide</format>
</record>
La meg først peke ut relasjonen mellom /guides/index-presentation/format/@name 'CollectionGuide' og /guides/record/format 'CollectionGuide'; hver <record> har potensielle <subject> og <format> elementer som vi vil sortere og gruppere etter. Husk at vi hadde to XSLT-er, èn for hver type gruppering. En av de viktigste grunnene til å skrive om all kode er at gruppering skal kunne skje automatisk.
Ingen av disse <record> elementene er nå sortert i XML-fila, og de kan puttes inn og flyttes hvor du vil. Det var den andre viktige grunnen til omskrivningen av koden - sortering kan også skje automatisk.
Her er en påminner fra gamle <topiclist> dager:
<TOPICLIST>
<TOPICNAME>Miscellaneous</TOPICNAME>
<BOOKMARK>misc</BOOKMARK>
</TOPICLIST>
Vær oppmerksom på <BOOKMARK> elementet som nå er borte i det nye skjemaet. Denne ble brukt til å lage lenker og ankere til grupperinger, men siden disse grupperingene nå skjer automagisk er <BOOKMARK> automagisk borte. Istedet bruker vi enkel filtrering for å gjøre navn om til URL-vennlige ankerpunkter. Dette betyr at hvis du vil legge til en ny gruppe så, eh, legger du bare til en ny gruppe der du trenger den. Resten er magi.
En rask sjekk forteller oss at XML-fila datt fra 1837 linjer ned til 890 linjer. Det er jo en god start med tanke på at de eksakt samme grunndata er bevart. Men la oss se hva som kan gjøres for XSLT-en.
Først, en parameter som vil være avgjørende for XSLT-ens liv og mening:
<!-- Parameter in; 'sort' is either 'subjects' or 'formats' -->
<xsl:param name="sort" select="'subjects'" />
Parameteren 'sort' (sorter) er, som XML-kommentaren sier, enten 'subjects' eller 'formats', som mer eller mindre taler for seg selv. Hvis ingen velges vil 'subjects' autmatisk bli valgt. Det betyr at vi nå kan gjøre som følger:
<xsl:choose>
<xsl:when test="$sort='subjects'">
<xsl:call-template name="html.create.bookmarks">
<xsl:with-param name="sort.by" select="$group.subjects" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$sort='formats'">
<xsl:call-template name="html.create.bookmarks">
<xsl:with-param name="sort.by" select="$group.formats" />
</xsl:call-template>
</xsl:when>
</xsl:choose>
Vi kaller opp malen 'html.create.bookmarks' med parameteren 'sort.by' med verdien enten '$group.subjects' eller '$group.formats'. La oss ta en titt på hvordan vi lagde variabelen '$group.subjects' i begynnelsen av XSLT-en, for der ligger hemmeligheten til alt!
Side 2: - Den fryktelige piraten Muenchian!
- Den fryktelige piraten Muenchian!
Siden vi jobber med gruppering trenger vi å trekke pusten dypt, og deretter bruke våre ferdigheter innen sort magi og en metode som kalles den Muenchianske Metode. Dette faktum er i seg selv mye av grunnen til det meste av forferdelig XSLT-kode verden over. Dette er hemmeligheten: Lær deg den Muenchianske metode, og det vil gå deg bra.
<xsl:key name="group.subjects.key" match="/guides/record/subject" use="." />
<xsl:variable name=" group.subjects"
select="/guides/record/subject[generate-id(.)=generate-id(key(' group.subjects.key', .))]" />
La oss meget forsiktig ta en titt på denne koden. Først lager vi et nøkkelsett som holder alle <subjects> i fila vår. Dette betyr alle sammen, dupliserte eller ikke. Finnes det en <subjects> vil den nå finnes i dette nøkkelsettet, men en av nøklene (!!) til gruppering er å bli kvitt alle dupliserte elementer. Måten å gjøre dette på er, som før nevnt, sort magi, og den første som fant ut av denne metoden var Steve Muench, derav metodens navn. Man tager sitt nøkkelsett, og med dette lager man en ny variabel 'group.subjects' sammen med funksjonen 'generate-id()'.
Vi tar vår liste med <subjects>, vårt nøkkelsett inkludert alle duplikater, og så velger de samme elementer igjen (dvs. all <subjects> elementer) med det kriterie at elementets unike identitet (som vi får ved å kalle et elements 'generate-id()' funksjon) er den samme som i det originale nøkkelsettet. Forklaringer her er at generate-id() gir oss en unik identifikator for et element i elementlista vår, i tillegg også i nøkkelsettet som peker til samme elementliste. Hvis elementer er identiske (har samme identitet) vil de bli valgt, men har de samme navn men forskjellig identitet vil de ikke bli valgt.
La oss lage en sekvens av imaginære emner; 'ABCCDB', og la oss hente de unike identifikatorene: 'A(1) B(2) C(3) C(4) D(5) B(6)'. For å lage en ny liste med duplikater tatt ut sjekker vi identiteten til gamle elementer mot nye. Dette foregår slik: "For element A med identitet 1, er identikatoren for dette elementet (1) den samme som elementet funnet i vårt nøkkelsett (det første innslaget av elementets navn vil alltid være 1)?" Vi spør altså om vårt element er det samme som det første elementet med samme navn i nøkkelsettet. Et duplikat vil ha samme navn, men en annen identifikator. Det vil derfor ikke velges inn i variabelen vi nå lager.
Så vi setter opp et nøkkelsett av alle emneelementer. Deretter lager vi en ny variabel som inneholder alle emneelementer som har samme identifikasjon som første innslag av det navnet, altså kun unike navn. Vi er nå frie til skrive kode som kan gruppere data etter denne nye elementlista. La oss se på hvordan det kan gjøres:
<xsl:call-template name="html.create.records">
<xsl:with-param name="sort.by" select="$group.subjects" />
</xsl:call-template>
Her kaller vi malen 'html.create.records' med parameteren 'sort.by' som er vår nye unike elementliste. Denne, som med 'html.create.bookmarks', kan byttes avhengig av '$sort'- variabelen (parameteren inn til XSLT-en). La oss nå se hva vi finner i 'html.create.records':
<xsl:template name="html.create.records">
<xsl:param name="sort.by" />
<xsl:for-each select="$sort.by">
<xsl:sort select="." />
For hvert element i 'sort.by' - som ved dette punktet betyr vår elementliste av unike emner - sorterer vi etter elementets innhold (som betyr navnet på elementet), og gjør følgende;
<xsl:variable name="this" select="." />
<xsl:variable name="records"
select="//record[format = $this or subject = $this]" />
Vi lager en variabel som inneholder en referanse til gjeldende element fordi vi vil referere til denne senere. Dette vil vi gjøre etter at konteksten er endret. Vi lager en annen variabel som inneholder en liste over alle 'record'-elementer som har gjeldende emne eller format (basert på vår unike elementliste). Dette betyr alle 'record'-elementer med et emne eller format som har samme navn som emnet eller formatet vi nå grupperer etter.
Side 3: forts. - Den fryktelige piraten Muenchian!
La oss se på hvordan vi lager et anker til denne grupperingen:<!-- Using translate() here to convert spaces to underlines in names and links -->
<a><xsl:attribute name="name">
<xsl:value-of select="translate($this, ' ', '_')" />
</xsl:attribute></a>
Se, det er ikke så vanskelig: vi bruker 'translate()' funksjonen til å oversette alle mellomrom til understreker. Vi kan putte inn flere regler for oversetting, men for denne jobben var dette alt som trengtes.
Visuelt legger vi alle våre lenker til ankrene på den venstre siden, og har alle våre grupperinger med anker på den høyre siden. La oss starte med å få tak i navnet på gruppen. Dette kan være èn av to mulige; enten navnet på emnet, eller tatt fra XML-filas del som inneholder tabellen med <index-presentation>-elementer. Disse er alternative navn på gruppene, og som vi kanskje husker har vi i toppen av den nye XSLT-en:
<xsl:key name="naming.key" match="/guides/index-presentation/*" use="@name" />
Vi kan nå bruke dette nøkkelsettet for å finne navn dersom de eksisterer som alternativer:
| <xsl:variable name="label">
| <xsl:choose>
| <xsl:when test="string(key( 'naming.key', $this ))">
| <xsl:value-of select="key( 'naming.key', $this )" />
| </xsl:when>
| <xsl:otherwise><xsl:value-of select="$this" /></xsl:otherwise>
| </xsl:choose>
| </xsl:variable>
| <xsl:value-of select="$label" />
Så, hvis emnets (eller formatets) navn er funnet i 'naming.key'-nøkkelsettet, bruker vi denne. Hvis ikke bruker vi bare navnet som det er. Man kan kanskje lure på hvorfor jeg gjorde alt dette, og svaret er at ofte vil grupperingsnavnet vi prosesserer på være forskjellig fra navnet som dukker opp på skjermen. Kanskje du finner emner av typen 'båt' mens du vil ha en noe luftigere tittel, alà 'De flotte båtene på de syv hav.' Opsjoner er bra å ha.
Nå som vi har grupperingene sortert (!!) vil vi liste alle <record> elementer under den respektive gruppering. Dette er enkelt med denne påminnelsen fra tidligere kode:
<xsl:variable name="records"
select="//record[format = $this or subject = $this]" />
Så vi simpelthen skriver de ut slik:
<xsl:for-each select="$records">
<p class='record-title'><a>
<xsl:attribute name="href"><xsl:value-of select="url" /></xsl:attribute>
<xsl:value-of select="title" />
</a></p>
<p class='record-description'><xsl:value-of select="description" /></p>
</xsl:for-each>
Og så avslutter vi:
</xsl:for-each>
</xsl:template>
Her har du koden (XSLT-fil) og datagrunnlaget (XML-fil) for alle dine XSLT og XML behov.
Side 4: - Hva kan vi håpe å få ut av alt dette?
- Hva kan vi håpe å få ut av alt dette?
Denne og forrige artikkel har gitt deg noen bruddstykker av faktisk arkaisk kode som eksempler på hva du aldri, aldri i livet bør gjøre. Og i tillegg eksempler på hvordan du kan gjøre ting på en bedre og moderne måte som ikke gir fremtidige arkeologer hodepine. Jeg vet at hva som er beskrevet her bare skraper lett i overflaten og at alternativer til denne løsningen også eksisterer. Det skulle kanskje bare mangle.
Jeg håper også at jeg har gitt eksempler på hvordan man bør prøve å legge opp XML og XSLT i fremtidige prosjekter, slik som å ikke å måtte oppdatere XSLT hver gang man oppdaterer XML - applikasjonen bør være datadrevet. Jeg håper videre:
- At sort magi også blir en viktig del av livet ditt.
- At du ikke lar deg skremme av den Muenchianske metodes dystre ytre.
- At dårlige skjemaer ikke står i veien for god XSLT.
- At du separerer data og applikasjon så mye du bare orker.
- Til sist håper jeg at du ser at det ofte er størrelsen det kommer an på - jo mindre dess bedre...
Det finnes også en siste ting jeg håper på - men den involverer prinsesser, pirater og denslags. Den er dessuten så omfangsrik at den krever en artikkel for seg selv. Inntill da, god XSLT!
--
"Ultimately, all things are known because you want to believe you know."
- Frank Herbert
__ http://shelter.nu/ __________________________________________________