Fachpraktikum 1590 „Erweiterbare Datenbanksysteme“ Aufgaben Phase 2 Wintersemester 2006/2007 Ralf Hartmut Güting, Dirk Ansorge, Thomas Behr, Markus Spiekermann Praktische Informatik IV, FernUniversität in Hagen 58084 Hagen – 1 – Vorwort Liebe Studierende, hiermit halten Sie nun die Aufgabenvorschläge zur zweiten Phase des Fachpraktikums in den Händen. Arbeiten Sie die Aufgabenstellungen bitte durch und überlegen Sie sich Fragen dazu. Eine Zuteilung von Aufgaben zu Gruppen erfolgt während der zweiten Präsenzphase. Innerhalb der Gruppen findet dann eine ausführliche Diskussion der zugeteilten Aufgabe mit dem Ziel, eine möglichst genaue Spezifikation der Programmierarbeiten zu erstellen, statt. Die drei zur Auswahl stehenden Aufgaben beschäftigen sich mit i) der Einbettung von Web-spezifischen Datentypen und der Speicherung von Webseiten in SECONDO, ii) einem Datentyp für Schachpartien und der Bereitstellung einiger Operatoren dafür und iii) der Implementierung einer Algebra für Graphen in SECONDO. Viel Spaß mit den Aufgaben Ihre Praktikumsbetreuer – 1 2 – Erstellung einer Algebra zur Darstellung von Webseiten 1.1 Einleitung Mehr oder weniger wichtige Informationen werden im World Wide Web im Regelfall durch Dokumente im HTML1-Format beschrieben. Neben der reinen textuellen Darstellung können zusätzlich Bilder, Videos oder Sounds in solchen Dokumenten eingebettet sein. Diese komplexen Objekte sind nicht Bestandteil der eigentlichen HTML-Datei, sondern existieren als externe Dateien, die durch Hyperlinks referenziert werden. Auch Darstellungsinformationen (StyleSheets) oder Reaktionen auf Benutzeraktionen (Javascript) können als externe Datei vorliegen. Im Gegensatz zu den davorgenannten Objekten ist es hier auch möglich, diese direkt innerhalb der html-Datei einzubinden. Durch die Lösung dieser Aufgabe soll SECONDO befähigt werden, komplette Webseiten mit allen dazugehörigen Objekten zu verwalten. Dazu sind geeignete Datenstrukturen zu entwerfen. Zusätzlich sollen auch Webseiten direkt aus dem WWW geladen werden können. Über entsprechende Operationen soll es möglich sein, ähnliche Webseiten zu erkennen. Ähnlichkeit kann sich hierbei auf den Inhalt oder die Struktur, d.h. den Aufbau des Dokuments, beziehen. Eine Erweiterung der Benutzerschnittstelle von SECONDO soll die Möglichkeit bieten, die in einer Datenbank gespeicherten Dokumente anzuzeigen und innerhalb einer Relation, die verschiedene Webseiten enthält, zu navigieren. 1.2 1.2.1 Beschreibung der Algebra Informelle Beschreibung Die Algebra, die Sie implementieren, soll mindestens die Typen url, html und page enthalten, die im folgenden zusammen mit einigen Operationen zunächst informell beschrieben sind. Die genauen Signaturen finden Sie in Abschnitt 1.2.2. • url Eine URL2 besteht aus dem Protokoll und dem Ort einer Ressource im WWW. Der Ort setzt sich aus dem Hostnamen bzw. der IP-Adresse eines Computers sowie dem Dateinamen auf dem betreffenden Computer zusammen. Ihre Algebra soll das Erzeugen von URL’s aus Strings und Texten ermöglichen. Zusätzlich sollen die drei genannten Komponenten aus einer gegebenen URL extrahiert werden können. Um eine Indexierung der Seiten über die URL zu ermöglichen, leiten Sie Ihren Datentyp von IndexableStandardAttribute ab, und implementieren Sie die dort geforderten Funktionen. • html Dieser Datentyp stellt eine einzelne HTML-Datei ohne eingebettete Objekte dar. Neben der Extraktion von Informationen (Text, enthaltene URL’s von eingebetteten und externen Objekten, Quell-URL, Datum der letzten Änderung, Meta-Informationen usw.) soll Ihre 1. HyperText Markup Language 2. Uniform Resource Locator – 3 – Algebra auch Funktionen bereitstellen, die eine Analyse der Struktur eines HTML-Dokuments ermöglichen. So sollen auch Informationen über die Anzahl der enthaltenen Tabellen etc. abrufbar sein. Weiterhin soll ein Operator existieren, der die Ähnlichkeit von HTMLDokumenten bzgl. Ihrer Struktur berechnet. Bei der Strukturähnlichkeit soll angegeben werden können, bis zu welcher Strukturtiefe, der Vergleich stattfinden soll. Ein negativer Wert entspricht dabei der vollständigen Tiefe beider Kandidaten. Zusätzlich soll angegeben werden können, ob die Reihenfolge der Strukturelemente mit einbezogen werden soll, oder ob es egal ist, ob ein Textabschnitt von einem Bild gefolgt wird oder ob das Bild vor dem Text steht. • page Der Datentyp page speichert eine vollständige Webseite. Neben der reinen html-Datei, die natürlich über einen Operator abgefragt werden kann, enthält dieser Datentyp zusätzlich alle eingebetteten Objekte. Überlegen Sie, wie Sie unterschiedliche Anzahlen solcher Objekte durch eine feste Anzahl FLOB’s repräsentieren können. Die eingebetteten Objekte sollen durch einen Operator abrufbar sein. Dieser liefert einen Tupelstrom, der neben weiteren Informationen auch das Objekt selbst als Binärdatei enthält. Neben den verschiedenen Datentypen mit ihren Operationen sollen Operatoren (wget und pageget) implementiert werden, die Web-Seiten direkt laden können. Diese beiden Operationen haben die gleichen Argumente, liefern jedoch unterschiedliche Ergebnistypen. Während der pageget-Operator einen Tupelstrom mit Attributen vom Typ url und page erzeugt, produziert der wget-Operator Tupelströme mit den Attributtypen url, string, binfile, wobei der String eine Typbeschreibung (MIME1 Typ ) (z.B. text/html oder application/pdf) darstellt. Beide Operatoren sollen mehrere Parameter akzeptieren. Das erste Argument dieser Operationen ist vom Typ url und kennzeichnet die initial zu ladende Webressource. Handelt es sich hierbei nicht um eine HTML-Datei, so gibt der pageget -Operator einen leeren Strom zurück, der wgetOperator einen Strom mit einem einzelnen Tupel. Der zweite Parameter vom Typ bool gibt an, ob Hyperlinks rekursiv verfolgt werden sollen. Hierbei werden alle HTML-Dokumente nach ihren externen Links untersucht und diese ebenfalls in den Ausgabe-Strom übernommen. Beachten Sie, daß es oft zyklische Abhängigkeiten zwischen den einzelnen HTML-Dokumenten gibt. Dateien sollen nur einmal in den Strom eingefügt werden. Verwenden Sie daher eine geeignete Datenstruktur, die die bereits verarbeiteten URL’s speichert. Der nächste Parameter gibt die maximale Rekursionstiefe (Typ int) an, falls das zweite Argument true ist; anderenfalls wird dieser Parameter ignoriert. Ein negativer Wert kennzeichnet eine unendliche Rekursionstiefe. Standardmäßig sollen beide Operationen nur Dateien verarbeiten, die von der gleichen Quelle (Host) stammen. Im dritten Parameter (Typ text) soll zusätzlich eine komma-separierte Liste von weiteren Hosts angegeben werden können, auf die die Verarbeitung ausgedehnt werden soll. Der letzte Parameter ist eine Funktion url → bool, die Bedingungen für zu verarbeitende URL’s definiert. Überlegen Sie, ob ggf. weitere Argumente wichtig sein könnten und ergänzen Sie die Parameterliste entsprechend mit optionalen Argumenten. Beide Operatoren sollen „nur“ das HTTP2-Protokoll unterstützen. Wenn Sie möchten, dürfen Sie beide Operatoren natürlich auf beliebige weitere Protokolle ausweiten. 1. Multipurpose Internet Mail Extension 2. Hyper Text Transfer Protocol – 1.2.2 4 – Signatur der Algebra Die Signatur der Algebra finden Sie in der folgenden Tabelle. algebra web sorts url, html, page ops protocol: url → text host: url → text filename: url → text source: {html, page} → url createurl: text → url content: html → text urls: {html, page} → stream(url) containsurl {html, page} × url → bool last_modified: html → instant metainfo: html × string → text metainfos: html → stream(tuple( [Key: string, Content: text])) number_of: html × string → int similar: html × html × int × bool → real extracthtml: page → html numOfFiles: page → int getFiles: page → stream(tuple( [Source: url, Type: string, File: binfile])) wget: url × bool × int × text × map: url → bool → stream(tuple( [Source: url, Type: string, File: binfile])) pageget: url × bool × int × text × (url → bool) → stream(tuple( [Source: url, Page: page])) =: ∀t ∈{url, html, page}: t×t → bool 1.3 Erweiterung der Javagui Implementieren Sie einen neuen Viewer für die Datentypen Ihrer Algebra. Es soll zusätzlich möglich sein, daß ganze Relationen mit Attributen vom Typ page angezeigt werden können. Klickt man innerhalb einer Seite auf einen Link, so soll geprüft werden, ob die dahinterliegende URL in – 5 – der Relation vorhanden ist und die entsprechende Seite soll dann ggf. angezeigt werden. Falls mehrere Tupel die gleiche URL enthalten, soll der Benutzer die von ihm gewünschte Seite auswählen können. Verwenden Sie zur Implementierung Ihres „Browsers“ ausschließlich Klassen der Java-Standard-Bibliothek (Version 1.4.2). 1.4 Hinweise Eine gute Beschreibung des HTML-Formats finden Sie unter http://de.selfhtml.org. Die vollständige HTML-Spezifikation ist unter: http://www.w3.org/TR/html401 abrufbar. Das http-Protokoll ist in der Version 1.1 im Dokument RCF12616 beschrieben. Leider gibt es größere Unterschiede in der Netzanbindung innerhalb verschiedener Betriebssysteme. Um Kompatibilität zu gewährleisten, sollen die Operationen wget und pageget nicht direkt auf den C bzw. C++ Bibliotheken aufsetzen. Stattdessen soll die Kommunikation mittels der in SECONDO vorhandenen Socket-Klasse erfolgen, der für verschiedene Betriebssysteme unterschiedliche Implementierungen zugrundeliegen. Die Interfacebeschreibung finden Sie im SECONDO-Verzeichnis unter include/SocketIO.h. 1. Request For Comments – 2 6 – Ein Datentyp für Schachpartien Um diese Aufgabe bearbeiten zu können, müssen Sie die Grundregeln des Schachspiels kennen (lernen). Tiefergehende Kenntnisse sind nicht erforderlich. In diesem Projekt soll eine Algebra implementiert werden, die es ermöglicht, Schachpartien zu verwalten. Zum Thema Schach gibt es viele Programme, die notierte Partien in verschiedenen Formaten verwalten können. Ein offenes und weit verbreitetes Format ist PGN1 (Portable Game Notation). Im Internet findet man dazu unter verschiedenen Quellen große Partiensammlungen2. Eine Partie im PGN-Format sieht etwa folgendermaßen aus: [Event "Tal Memorial"] [Site "Moscow RUS"] [Date "2006.11.14"] [Round "7"] [White "Ponomariov,R"] [Black "Aronian,L"] [Result "1/2-1/2"] [WhiteElo "2703"] [BlackElo "2741"] [EventDate "2006.11.06"] [ECO "C88"] 1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 O-O 8. a4 b4 9. a5 d5 10. exd5 e4 11. dxc6 exf3 12. d3 fxg2 13. Qf3 Rb8 14. Bc4 Ne8 15. Bf4 Bf6 16. Nd2 Bxb2 17. Rab1 Bc3 18. Bg5 Bf6 19. Rxe8 Rxe8 20. Ne4 Rxe4 21. Bxf6 Qxf6 22. Qxf6 gxf6 23. dxe4 Kf8 24. Kxg2 Ke7 25. Rd1 1/2-1/2 In der obigen Notation bezeichnen die Buchstaben von a-h die Spalten (im Schach Linien genannt, engl.: files) und die Ziffern von 1-8 die Reihen des Spielbretts. Dabei wird der Figurentyp durch einen Buchstaben der englischen Bezeichnung abgekürzt (Pawn~Bauer, Knight~Springer, Bishop~Läufer, Rook~Turm, Queen~Königin, King~König). Nun zieht abwechselnd eine weiße und eine schwarze Figur, die Bewegung einer Figur bezeichnet man als Halbzug. Die obige Notation ist recht kompakt, d.h. es wird nur die Figur und das Zielfeld definiert (bei Bauernzügen entfällt die Figurbezeichnung). Sollte dies nicht reichen, so wird durch die zusätzliche Angabe von Linie und/oder Reihe Eindeutigkeit erreicht. Eine präzise Definition der Notation findet man in den FIDE-Regeln3. Die Algebra soll einen Datentyp chessgame besitzen, dessen interne Datenstruktur eine Partie speichern kann, d.h. die Züge (ein Zug besteht aus einer Bewegung von Weiß und Schwarz, das Setzen einer einzelnen Figur bezeichnet man als Halbzug) und im PGN-Format enthaltene MetaInformationen, z.B. Namen der Spieler, Datum des Spiels, Ergebnis, etc. Zusätzlich sollen aus der Zugfolge auch weitere Informationen abgeleitet werden können, z.B. die Stellung oder die verbleibenden Figuren (Material) nach einem bestimmten Halbzug, etc. Der Viewer soll das Anzeigen und Editieren von Spielen unterstützen. Zusätzlich soll es möglich sein, Suchkritierien und Stellungen einzugeben, aus denen dann eine Query erzeugt wird. Dazu muss natürlich ein vordefiniertes Datenbankschema festgelegt werden, über dem SQL-Anfragen 1. Zur Dokumentation des PGN-Formats siehe z.B. http://tim-mann.org/xboard.html 2. http://www.schachbund.de/links/_Internet/Archive 3. FIDE (Fédération Internationale des Échecs), siehe z.B. http://www.schachbund.de/fideregeln – 7 – formuliert werden können. Für Standardattribute sollten Indexe angelegt werden, um Suchen zu beschleunigen. Suchergebnisse sollen als PGN-Dateien exportiert werden können. Spezifikation der Algebra Es sollen die Datentypen chessgame, position, move, material implementiert werden. Der Typ chessgame stellt eine komplette Partie dar, position eine Stellung und move einen Halbzug Der Typ material stellt eine Menge von weißen und schwarzen Figuren dar; damit kann z.B. nach Stellungen gesucht werden, die dieses Figurenmaterial enthalten. Grundlegende Operationen über diesen Typen sind : • • • • • • • getkey positions moves getposition getmove pieces moveNo : : : : : : : chessgame x string -> string chessgame -> stream(position) chessgame -> stream(move) chessgame x int -> position chessgame x int -> move position -> material position -> int, move -> int Damit lassen sich aus einer Partie detailliertere Informationen gewinnen. Die Operation getkey akzeptiert die folgenden Schlüsselwörter: name_{w,b} (Name des Spielers der die weißen bzw. schwarzen Steine spielt), rating_{b,w}, event, site, date, result, eco_code, moves, ggf. weitere Metadaten eine Spieles. Die Operationen getposition und getmove ermitteln die Stellung bzw. die Figurbewegung des angegebenen Halbzugs. Mittels pieces kann das Material einer Stellung berechnet werden und moveNo bestimmt zu einer Stellung oder einem Zug die Nummer des Halbzugs der zugehörigen Partie. Charakteristika von Zügen lassen sich über die folgenden Operationen bestimmen: • • • • • • • • agent : captured : startrow : endrow : startfile: endfile : check : captures : move move move move move move move move -> -> -> -> -> -> -> -> string string int int string string bool bool Die Operationen agent und captured ermitteln den engl. Figurennamen einer bewegten oder gefangenen (geschlagenen) Figur bzw. den Wert “none“. Dabei werden weiße Figuren durch einen großen, Schwarze durch einen kleinen Anfangsbuchstaben gekennzeichnet, z.B.: weiße Dame ~ “Queen“, schwarzer Springer ~ “knight“. Linien werden durch die Stringwerte “a“ bis “h“ und Reihen durch die Integerwerte 1-8 identifiziert. Über die Prädikate check und captures kann man testen, ob in einem Zug Schach geboten bzw. eine Figur gefangen wird. Tests auf Stellungen oder Teilstellungen können mittels der folgenden Operationen durchgeführt werden: • • range : position x string x int x string x int -> position includes : position x position -> bool Durch Angabe des linken, unteren Feldes und des rechten, oberen Feldes liefert der range Operator eine Stellung, die nur die Figuren aus dem angegebenen Bereich enthält, z.B. definiert der Ausschnitt “a“,1,“a“,8 die gesamte Linie a. Der Operator includes liefert TRUE, wenn die – 8 – erste Stellung in der Zweiten enthalten ist, d.h. jede Figur des ersten Arguments ist in der zweiten Stellung auf demselben Feld. Um die Suche nach ähnlichen Stellungen zu ermöglichen, gibt es die Operationen • • • count : material x string -> int count : material x int -> int =, < : material x material -> bool die die Anzahl der Figuren eines bestimmten Typs bzw. die Anzahl aller Figuren ermitteln oder einen Vergleich des Materials erlauben. Da die Bewegung einer Figur auch als moving point aufgefaßt werden kann, soll es möglich sein, eine komplette Schachpartie in eine Menge von Tupeln des Schemas [Piece: string, White: bool, Route: mpoint] zu konvertieren. Jede Figur wird so durch ein Tupel dargestellt, welches die Information über all seine Bewegungen enthält. Dazu soll der Operator • movingpoints : chessgame -> stream(tuple([Piece: string, White: bool, Route: mpoint])) implementiert werden. Beispielanfragen Angenommen games sei eine Relation vom Typ rel(tuple([Match: chessgame])), in welchen Partien wurde die weiße Dame gefangen? query games feed extendstream[Move: .Match moves] filter[.Move captured “Queen“] consume; Finde alle Paare von Partien, in denen nach dem 20. Zug dasselbe Material bzw. dieselbe Stellung auf dem Brett steht: let games_20 = games feed extend[Pos20: getpos(40, .Match)] extend[Mat20: .Pos20 pieces] consume; query games_20 feed games_20 feed {x} hashjoin[Mat20, Mat20_x, 9997] consume; query games_20 feed games_20 feed {x} hashjoin[Pos20, Pos20_x, 9997] consume; Finde alle Paare von Partien, in denen nach dem 20. Zug eine Stellung in der Stellung der anderen enthalten ist, jedoch nur bezogen auf das Zentrum des Spielbretts: query games_20 feed games_20 feed {x} symmjoin[range(.Pos20, “c“, 3, “f“, 8) includes range(..Pos20, “c“, 3, “f“, 8)] consume; – 3 9 – Graphen in SECONDO In der Informatik werden Graphen häufig verwendet, um Beziehungen zwischen Objekten zu beschreiben und zu visualisieren. Die Anwendungsfelder reichen dabei von E/R-Diagrammen über Flußdiagramme bis hin zu Netzwerken. In SECONDO sollen nun Typen zur Darstellung von gerichteten, kantenbewerteten Graphen bereit gestellt werden, nämlich die Typen graph, edge, vertex und path. Neben der Möglichkeit, Graphen in SECONDO direkt als Konstanten zu definieren, soll es erlaubt sein, sie auch aus Relationen bzw. Tupelströmen zu erzeugen. Dabei wird das Ergebnis eines join-Operators eingesammelt und als Kantenmenge eines Graphen interpretiert. Zu beachten ist, daß der Anwender vor der Benutzung des dafür benötigten constGraph-Operators sicher stellen muß, daß alle verschiedenen Tupel aller verwendeten Relationen mit unterschiedlichen Nummern indiziert sind. Dies ist damit begründet, daß diese Indizierung bei der Graphkonstruktion zur Identifizierung gleicher Knoten verwendet wird. Zur Vereinfachung der späteren Visualisierung kann für jeden Knoten eine räumliche Information in Form eines point-Objektes angegeben werden. Diese Information wird dazu genutzt, die Darstellung eines Graphen, der aus geometrischen Objekten abgeleitet wurde, in einem SECONDOViewer einigermaßen realitätsgetreu zu halten. Neben den oben angegebenen Typen soll die zu implementierende GraphAlgebra noch mindestens die folgenden Operatoren enthalten: • constGraph: stream(Tuple) x int_attr x int_attr x (Tuple -> real) -> graph Erzeugt einen Graphen aus einem Tupelstrom. Die Attributnamen geben Attribute vom Integer-Typ für die oben beschriebenen Tupelnummern an, während der vierte Parameter für die Kantenmarkierung verwendet wird. • constGraph: stream(Tuple) x int_attr x int_attr x (Tuple -> real) x point_attr x point_attr -> graph Arbeitet wie der vorige Operator. Zusätzlich werden Attributnamen von point-Objekten für die beiden Knoten übergeben. • vertices: graph -> stream(tuple([Vertex: vertex])) Erzeugt einen Strom von vertex-Tupeln, der alle Knoten des Graphen in ungeordneter Reihenfolge enthält. • edges: graph -> stream(tuple([Edge: edge])) Erzeugt einen Strom von edge-Tupeln, der alle Kanten des Graphen in ungeordneter Reihenfolge enthält. • equal: graph x graph -> bool Liefert TRUE, falls beide Graphen gleich bzgl. ihrer Knoten und Kanten sind. Sonst FALSE. • partOf: graph x graph -> bool Liefert TRUE, falls der zweite angegebene Graph ein Teilgraph des ersten Graphen ist. Sonst FALSE. • connectedComponents: graph -> stream(tuple([Graph: graph])) Erzeugt einen Strom von Graphen, die den starken Zusammenhangskomponenten des angegebenen Graphen entsprechen. – 10 – • merge: graph x graph -> graph Die zwei angegebenen Graphen werden zu einem einzelnen Graphen verschmolzen. Knoten mit identischen Nummern werden einfach verschmolzen, während bei zwei gleichen Kanten, die aber eine unterschiedliche Bewertung haben, die „billigere“ Kante verwendet wird. • maxDegree/minDegree: graph -> int Gibt den maximalen/minimalen Grad des Graphen zurück. • theVertex: graph x int -> vertex Gibt den Knoten des gegebenen Graphen zurück, der den übergebenen Integer-Wert als Index verwendet. Ist der Wert nicht vorhanden, ist das resultierende vertex-Objekt undefiniert. • shortestPath: graph x vertex x vertex -> path Erzeugt ein path-Objekt, das den kürzesten Pfad vom ersten zum zweiten Knoten beschreibt. • edges: path -> stream(tuple([Edge: edge])) Erzeugt einen edge-Tupelstrom, in dem die Kanten in geordneter Reihenfolge geliefert werden. • vertices: path -> stream(tuple([Vertex: vertex])) Erzeugt einen vertex-Tupelstrom, in dem die Knoten in geordneter Reihenfolge geliefert werden. • circle: graph x vertex x real -> graph Erzeugt einen Teilgraphen des angegeben Graphen G, der, ausgehend von dem gegebenen Knoten k, alle weiteren Knoten (und die verbindenden Kanten) von G enthält, die maximal so weit entfernt von k sind, wie es der dritte Parameter angibt. Die Entfernung ist dabei als Netzwerkentfernung zu interpretieren, d. h. die Entfernung zweier durch eine Kante verbundenen Knoten ist genau gleich der Kantenbewertung dieser Kante. • key: vertex -> int pos: vertex -> point source: edge -> int target: edge -> int cost: edge -> real Mittels dieser Operatoren können die Daten von vertex- und edge-Objekten ausgelesen werden. Für die Berechnung der point-Objekte, die für den constGraph-Operator benötigt werden, ist es nötig, einen (überladenen) Operator centerPoint: (point, points, line, region) -> point zu implementieren, der einen Punkt berechnet, der das entsprechende Objekt möglichst gut repräsentiert. Zur Visualisierung von Graphen soll der Hoese-Viewer um entsprechende Funktionalitäten erweitert werden. Hierbei ist zu beachten, daß gerichtete Graphen dargestellt werden müssen, d.h. es müssen Kanten mit Pfeilen ergänzt werden. Der schwierigste Aspekt bei der Visualisierung von Graphen ist die Verteilung der Knoten in der Ebene. Sofern point-Objekte für die Knoten des Graphen vorhanden sind, sollen diese für die – 11 – Verteilung der Knoten genutzt werden. Andernfalls soll bei kantenbewerteten Graphen die Kantenbewertung für die Plazierung verwendet werden, d.h. Knoten die durch Kanten mit niedriger Bewertung verbunden sind, sollen räumlich näher zueinander plaziert werden als welche, die mit höherwertigen Kanten verbunden sind. Hierbei macht es Sinn, sich zunächst drei verbundene Knoten auszusuchen und diese in der Ebene gemäß ihrer Abstände zu plazieren. Alle anderen Knoten können dann relativ zu diesen Knoten plaziert werden. Für die Plazierung der Knoten, also die Berechnung von passenden point-Objekten, ist der folgende Operator zu implementieren: • placeNodes: graph -> graph Erzeugt bzw. verändert die point-Objekte des übergebenen graph-Objekts derart, daß entsprechend der obigen Beschreibung - eine möglichst gute Darstellung im Viewer erreicht wird. Der Hoese-Viewer soll vor der Anzeige eines graph-Objekts prüfen, ob für jeden Knoten pointDaten vorhanden sind. Ist dies nicht der Fall, wird eine Anzeige des Graphen mit einer entsprechenden Fehlermeldung verweigert. Infolgedessen sollte in den meisten Fällen ein placeNodesAufruf vor der Anzeige des Graphen erfolgen.