mirror of
https://github.com/bcye/structured-wikivoyage-exports.git
synced 2025-04-20 09:46:20 +00:00
initial sketch
This commit is contained in:
commit
e242a18408
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.12
|
10
pyproject.toml
Normal file
10
pyproject.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[project]
|
||||
name = "mapvoyage-extract"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"mwparserfromhell>=0.6.6",
|
||||
"wikitextparser>=0.56.3",
|
||||
]
|
483
sketching/parsing.ipynb
Normal file
483
sketching/parsing.ipynb
Normal file
@ -0,0 +1,483 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#region text paste\n",
|
||||
"txt = \"\"\"{{pagebanner|Enschede banner.jpg|caption=The soccer Arke stadium of FC Twente in Enschede}}\n",
|
||||
"'''Enschede''' is a city in the [[Netherlands]]. It is the largest city of the Twente region (a former center of textile industry), and also of the [[Overijssel]] province. Enschede was designated the greenest town in the Netherlands in 2003.\n",
|
||||
"\n",
|
||||
"==Understand==\n",
|
||||
"[[Image:Enschede Oude Markt.jpg|thumb|350px|Oude Markt, Enschede]]\n",
|
||||
"On May 13, 2000, a fireworks storage in Enschede exploded, destroying an entire neighborhood and killing 23 people, including 4 firemen. This catastrophe is known in the Netherlands as the Vuurwerkramp, Dutch for ''fireworks disaster''. Since this event, the city has undergone a major reconstruction process. After the fall of the textile industry, Enschede strives to become a center of business, innovation and technology through the Business and Science park next to the University of Twente.\n",
|
||||
"\n",
|
||||
"As a city famous for being \"destroyed and reconstructed\" many times, Enschede does not offer much in terms of historical attractions. However, the city has nice displays of contemporary Dutch architecture, particularly in the Roombeek neighborhood and at University of Twente, and its nightlife, cultural vibrancy and shopping options are quite developed for a city of its size.\n",
|
||||
"\n",
|
||||
"==Talk==\n",
|
||||
"Official language is Dutch. Almost all understand English. Since Enschede is close to Germany, there are more tourist information brochures in German than English.\n",
|
||||
"\n",
|
||||
"==Get in==\n",
|
||||
"{{mapframe | 52.225 | 6.890 | height=600 | width=500 | zoom=14 }}\n",
|
||||
"===By train===\n",
|
||||
"From elsewhere in the Netherlands, Enschede is easily accessible by train from all major Dutch cities, and can be reached from smaller cities with at most one or two transfers. The journey time from Amsterdam is approximately two hours. An excellent (train only) travel planner is available at [http://ns.nl/ Nationale Spoorwegen]. Hourly intercity trains from [[Amsterdam Schiphol Airport]], [[The Hague]] and [[Zwolle]] terminate at {{marker|type=go|name=Enschede railway station|lat=52.222372|long=6.890495}}. If you need to use a connecting bus or ferry from within the Netherlands, use [http://9292.nl 9292].\n",
|
||||
"\n",
|
||||
"From Germany, direct trains (ending in Enschede railway station) are available from Gronau, Dortmund, and Münster. From Münster, it takes 1 hr 20 min by hourly regional train to Enschede. To reach Enschede from Berlin, a stopover is available in Hengelo (a small city about 10 minutes by train from Enschede). Further guidance is available at [http://www.db.de Die Bahn].\n",
|
||||
"\n",
|
||||
"===By bus===\n",
|
||||
"KLM provides free [https://www.klm.com/travel/ca_en/plan_and_book/ticket_information/travel_by_train_or_bus_on_a_KLM_ticket/index.htm bus transfer] for travellers arriving or departing on KLM flights from Amsterdam Schiphol airport. The bus leaves Amsterdam Schiphol twice a day, at 09:05 and 21:00. The entire ride takes 3 hours.\n",
|
||||
"\n",
|
||||
"Flixbus provides bus service from [https://www.flixbus.com/bus/enschede major cities Netherlands and Germany] to Enschede. Buses stop at Enschede train station and University of Twente.\n",
|
||||
"\n",
|
||||
"===By car===\n",
|
||||
"There is a large parking lot underneath the market square in the centre of the city, the H.J. van Heekplein, which provides approximately 2000 parking spaces for visitors arriving by car.\n",
|
||||
"\n",
|
||||
"There is a cheaper park-and-ride solution available at the expanded P+R Zuiderval. Frequent buses travel from here directly to the city center (approximately 5 minutes by bus). This is especially useful if you are arriving in Enschede via the highway, as the lot is just off the highway exit.\n",
|
||||
"\n",
|
||||
"==Get around==\n",
|
||||
"\n",
|
||||
"Enschede is a city best explored by bicycle, on foot or by public transportation. Parking spaces can be difficult to find and there are often traffic jams, especially during rush hour. The city center is also closed to motorized vehicles.\n",
|
||||
"\n",
|
||||
"Buses in Enschede do not accept cash. Payment by debit or credit cards are required.\n",
|
||||
"\n",
|
||||
"==See==\n",
|
||||
"* {{see\n",
|
||||
"| name=Elderinkshuis | alt= | url= | email=\n",
|
||||
"| address=De Klomp 35 | lat=52.219634 | long=6.900369 | directions=\n",
|
||||
"| phone= | tollfree= | fax=\n",
|
||||
"| hours= | price=\n",
|
||||
"| content=The oldest building in the city (other than the Grote Kerk), built in 1783. It doesn't look extraordinary but it has the feat of surviving the city's many disasters.\n",
|
||||
"}}\n",
|
||||
"* {{see\n",
|
||||
"| name=Grote Kerk | alt= | url= | email=\n",
|
||||
"| address=Oude Markt | lat=52.220773 | long=6.896050 | directions=\n",
|
||||
"| phone= | tollfree= | fax=\n",
|
||||
"| hours= | price=\n",
|
||||
"| content=Enschede's \"central church\" is also the oldest building of the city, dating from the Middle Ages (1200), although it has undergone several expansions and reforms across the time. It is now mainly used for weddings and concerts. The sundial in the right side of the church (looking at the entrance) was crafted in 1836.\n",
|
||||
"}}\n",
|
||||
"* {{see\n",
|
||||
"| name=De Museumfabriek | alt=Museum TwentseWelle | url=http://www.demuseumfabriek.nl | email=\n",
|
||||
"| address=Het Rozendaal 11 | lat=52.232357 | long=6.895115 | directions=\n",
|
||||
"| phone=+31 53 480-7680 | tollfree=\n",
|
||||
"| hours= | price=€12.50\n",
|
||||
"| lastedit=2023-02-09\n",
|
||||
"| content=A new museum set in the heart of the modern quarter Roombeek, which was destroyed in the Fireworks Disaster of 2000, Twentse Welle tells the story of mankind in Twente, the region in which Enschede is situated. While there are a number of exhibits that are visually appealing to speakers of any language, the museum, unlike many in the Netherlands, has information about most of its exhibits only in Dutch and German.\n",
|
||||
"}}\n",
|
||||
"* {{see\n",
|
||||
"| name=Rijksmuseum Twenthe | alt= | url=https://www.rijksmuseumtwenthe.nl/ | email=info@rijksmuseumtwenthe.nl\n",
|
||||
"| address=Lasondersingel 129-131 | lat=52.227778 | long=6.896944 | directions=\n",
|
||||
"| phone=+31 53 438675 | tollfree= | fax=+31 53 4359002\n",
|
||||
"| hours=Tu-Su 11:00-17:00 | price=Adult €15, senior or student €7, child 18 and under free\n",
|
||||
"| wikidata=Q1505892\n",
|
||||
"| lastedit=2019-12-01\n",
|
||||
"| content=Large collection of 18th-century art and contemporary art.\n",
|
||||
"}}\n",
|
||||
"[[Image:Roombeek enschede 2.jpg|thumb|250px|Roombeek neighborhood]]\n",
|
||||
"* {{see\n",
|
||||
"| name=Roombeek | alt= | url=http://www.roombeek.nl/ | email=\n",
|
||||
"| address= | lat=52.2297 | long=6.8961 | directions=10 min walk north from Enschede Centraal station\n",
|
||||
"| phone= | tollfree=\n",
|
||||
"| hours= | price=\n",
|
||||
"| wikidata=Q2018838\n",
|
||||
"| content=Roombeek is the neighborhood between Deuningerstraat and Oldenzaalstraat, north of Lasondersingel, which corresponds to the neighborhood rebuilt after the fireworks disaster. It is now the finest neighborhood of Enschede, with all houses and buildings projected by renowned architects, plenty of green areas and exclusive lanes for buses and bicycles, and containing the Twentse Welle museum and the Rijksmuseum.\n",
|
||||
"}}\n",
|
||||
"* {{see\n",
|
||||
"| name=Stadshaard Enschede | alt= | url= | email=\n",
|
||||
"| address=Deurningerstraat, Roombeek | lat=52.231287 | long=6.888673 | directions=\n",
|
||||
"| phone= | tollfree= | fax=\n",
|
||||
"| hours= | price=\n",
|
||||
"| content=Enschede's heating and power station, which was (perhaps unfairly) elected the \"ugliest building of Netherlands\" by a poll of the local newspaper TC Tubantia. It looks somewhat like a church covered with Delft-blue tiles. It is richly illustrated as a whole and artist Hugo Kaagman has placed images on the tiles that are associated with the production of electricity and well-known icons from Enschede's history, such as Jan Cremer and the Grolsch swing-top bottle.\n",
|
||||
"}}\n",
|
||||
"* {{see\n",
|
||||
"| name=Synagogue Enschede | alt= | url= | email=\n",
|
||||
"| address=Prinsestraat 14 | lat=52.218019 | long=6.885977 | directions=\n",
|
||||
"| phone=+31 53 4324507 | tollfree=\n",
|
||||
"| hours=Tu-Th, Su 11:00-17:00 | price=€7.50\n",
|
||||
"| content=Reputedly the most beautiful synagogue of the Netherlands. Tours available on Sunday at 14:00 (in Dutch). English tours are sporadically available.\n",
|
||||
"}}\n",
|
||||
"* {{see\n",
|
||||
"| name=University of Twente | alt=Universiteit Twente | url=https://www.utwente.nl | email=\n",
|
||||
"| address=Drienerlolaan 5 | lat=52.241102 | long=6.851912 | directions=\n",
|
||||
"| phone=+31 53 489 9111 | tollfree=\n",
|
||||
"| hours= | price=\n",
|
||||
"| wikidata=Q1547084\n",
|
||||
"| content=The university has a number of buildings with interesting architecture, such as the '''Drienerlo tower''' (Torentje van Drienerlo), the '''Cubicus''' and the student dormitory at the crossing between Campuslaan and Matenweg.\n",
|
||||
"}}\n",
|
||||
"* {{see\n",
|
||||
"| name=Volkspark | alt= | url=http://www.volkspark.nl/index.php | email=\n",
|
||||
"| address= | lat=52.220489 | long=6.879480 | directions=10 min walk from Enschede Centraal station\n",
|
||||
"| phone= | tollfree= | fax=\n",
|
||||
"| hours= | price=\n",
|
||||
"| content=The Volkspark is a beautiful park close to the city center. It was constructed in 1872 to serve as recreational area for the workers in the textile industry. It also hosts several cultural activities alongside the year, such as the spring and autumn Kermis.\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"==Do==\n",
|
||||
"[[Image:Grolsch veste.jpg | thumb | 300px | Grolsch Veste, stadium of FC Twente]]\n",
|
||||
"* Enjoy the art in the many galleries in town.\n",
|
||||
"* Take a guided tour through the nature parks surrounding Enschede or purchase a map with walking/cycling routes\n",
|
||||
"* '''Football:''' {{do\n",
|
||||
"| name=FC Twente | alt= | url=https://www.fctwente.nl/en/ | email=\n",
|
||||
"| address= | lat=52.2367 | long=6.8375 | directions=next to Enschede Kennispark station\n",
|
||||
"| phone= | tollfree=\n",
|
||||
"| hours= | price= | lastedit=2022-07-10\n",
|
||||
"| wikipedia=De Grolsch Veste | image=Grolsch Veste wedstrijd.JPG | wikidata=Q727389\n",
|
||||
"| content=They play football in the Eredivisie, the Dutch top tier, and usually do well and qualify for European tournaments. De Grolsch Veste stadium has a capacity of 30,000.\n",
|
||||
"}}\n",
|
||||
"* {{do\n",
|
||||
"| name=Go Planet Parc | url=http://www.go-planet.nl/ | email=\n",
|
||||
"| address= | lat= | long= | directions=next to Grolsch Veste and Enschede Kennispark station\n",
|
||||
"| phone= | tollfree= | fax=\n",
|
||||
"| hours= | price=\n",
|
||||
"|lastedit=2024-10-30| content=An entertainment park next to the Grolsch Veste football stadium, approximately midway between Enschede and Hengelo. It contains various facilities, including a cinema with 10 rooms, a 400-m-long ice skating track, a bowling, a diving school, a concert hall, a kart track, a couple of restaurants and others.\n",
|
||||
"}}\n",
|
||||
"* {{do\n",
|
||||
"| name=Het Rutbeek | alt= | url=http://www.hetrutbeek.nl | email=\n",
|
||||
"| address=Blikkersmaatweg 15 | lat=52.180771 | long=6.839242 | directions=\n",
|
||||
"| phone=+31 50861050 | tollfree= | fax=\n",
|
||||
"| hours=during the day | price=free\n",
|
||||
"| content=A lake and recreational area in the south of Enschede. Contains beaches (including a nudist one), forests and recreational facilities. it is also possible to practise diving here.\n",
|
||||
"}}\n",
|
||||
"* {{do\n",
|
||||
"| name=Wilminktheater | alt=Wenninkgaarde 40-42 7511 PH Enschede | url=http://www.wilminktheater.nl/ | email=\n",
|
||||
"| address=Wenninkgaarde 40-42, Oude Markt | lat=52.222748| long=6.895712 | directions=\n",
|
||||
"| phone=+31 53 485 8500 | tollfree= | fax=\n",
|
||||
"| hours=M-Sa 12:00-17:00 (ticket counter) | price=\n",
|
||||
"| content=Enschede's main theatre and concert hall. Performances are usually in Dutch.\n",
|
||||
"}}\n",
|
||||
"* {{do\n",
|
||||
"| name=Aquadrome | alt= | url=https://sportaal.nl/aquadrome | email=\n",
|
||||
"| address= | lat=52.20683 | long=6.89645 | directions=\n",
|
||||
"| phone= | tollfree= | fax=\n",
|
||||
"| hours= | price=\n",
|
||||
"| content=Swimming pool with two slides, a wave pool and wild water rapids.\n",
|
||||
"}}\n",
|
||||
"* {{do\n",
|
||||
"| name=Holland Casino Enschede | alt= | url=https://www.hollandcasino.nl/vestigingen/enschede | email=\n",
|
||||
"| address=Boulevard 1945 105 | lat=52.218037 | long=6.894599 | directions=\n",
|
||||
"| phone=+31 53 7502750 | tollfree=\n",
|
||||
"| hours= | price=\n",
|
||||
"| lastedit=2020-06-02\n",
|
||||
"| content=\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"==Learn==\n",
|
||||
"International Institute for Geo-Information and Earth Observation (known as ITC) is the most known educational/training center in Geoghraphic Information System and Remote Sensing. Also located here is the [http://www.utwente.nl/en University of Twente]. Other educational institutes include Saxion Hogeschool and many schools for children. More information at (http://cms3.enschede.nl/)\n",
|
||||
"\n",
|
||||
"==Buy==\n",
|
||||
"\n",
|
||||
"[[Image:Dragonheart medieval shop.jpg|thumb|250px|Dragonheart Medieval Lifestyle]]\n",
|
||||
"\n",
|
||||
"Enschede wishes to profile itself as the place to be in the eastern Netherlands when it concerns shopping. The large square, the Van Heekplein, has been modernised and been made the 'shopping-centre' of the city. Large stores such as V&D and Primark are there, as are many clothing stores such as H&M, WE and C&A. Many other shops can be found throughout the city centre. Every Thursday shops are opened until 21:00 instead of 18:00. All shops are open on Sundays, although they will usually open a few hours later and close a few hours earlier. \n",
|
||||
"\n",
|
||||
"On Tuesdays and Saturdays from 08:00 to 17:00, there is a large {{marker|type=buy|name=street market|lat=52.217874|long=6.897371}} at the Van Heekplein, which attracts quite a lot of Germans, as well as the locals of course. It offers bakeries, seafood, produce, cheese, clothing, takeout and other vendors. \n",
|
||||
"\n",
|
||||
"* {{buy\n",
|
||||
"| name=Dragonheart Medieval Lifestyle | url=http://www.dragonheart.nl | email=info@dragonheart.nl\n",
|
||||
"| address=Stroinksweg 90 | lat= | long= | directions=\n",
|
||||
"| phone=+31 53 4782005 | tollfree= | fax=\n",
|
||||
"| hours= | price=\n",
|
||||
"| content=An exotic medieval-themed shop where you can find all kinds of medieval clothes, jewelery, accessories, furniture, and even weapons and armor.\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"==Eat==\n",
|
||||
"\n",
|
||||
"Lots of bars and restaurants are located at the Oude Markt. The first weekend of September this square is the stage of the 'Proefeet', an event on which restaurants sell small samples of their best food in order to win a grand prize.\n",
|
||||
"\n",
|
||||
"===Budget===\n",
|
||||
"* {{listing | type=eat\n",
|
||||
"| name=burgerme Enschede | alt= | url=https://www.burgerme.nl/ | email=\n",
|
||||
"| address=Willem Wilminkplein 27 | lat=52.222417 | long=6.893084 | directions=\n",
|
||||
"| phone=0900 8608 (high cost) | tollfree=\n",
|
||||
"| hours=Su-Th 11:00-23:00, F Sa 11:00-00:00 | price=\n",
|
||||
"| lastedit=2019-12-01\n",
|
||||
"| content=Burger joint that stays open late and has a fleet of motorcycles and bicycles for delivery. Burgers start at €6.9 and combos start at €8.5.\n",
|
||||
"}}\n",
|
||||
"* {{listing | type=eat\n",
|
||||
"| name=Happy Italy | alt= | url= | email=\n",
|
||||
"| address=Willem Wilminkplein 31, 7511 PP | lat=52.222215 | long=6.893067 | directions=\n",
|
||||
"| phone=+31 53 744 0520 | tollfree=\n",
|
||||
"| hours= | price=Pastas ranging from €5.5 to €11\n",
|
||||
"| lastedit=2019-12-21\n",
|
||||
"| content=Italian restaurant with a large selection of pasta.\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"===Mid-range===\n",
|
||||
"\n",
|
||||
"* {{listing | type=eat\n",
|
||||
"| name=Het Paradijs | alt= | url=https://hetparadijs.com | email=info@hetparadijs.com\n",
|
||||
"| address=Nicolaas Beetsstraat 48 7514 CW | lat=52.227223 | long=6.894579 | directions=\n",
|
||||
"| phone=+31 53 436-7919 | tollfree=\n",
|
||||
"| hours=Th F 17:00-22:00, Sa Su noon-22:00 | price=\n",
|
||||
"| lastedit=2022-07-31\n",
|
||||
"| content=Vegetarian restaurant\n",
|
||||
"}}\n",
|
||||
"* {{listing | type=eat\n",
|
||||
"| name=Bardot | alt= | url=https://www.bardot.nl/ | email=\n",
|
||||
"| address=Langestraat 47c | lat=52.219941 | long=6.895695 | directions=\n",
|
||||
"| phone=+31 53 3034020 | tollfree=\n",
|
||||
"| hours= | price=Entrees start at €15\n",
|
||||
"| lastedit=2020-06-02\n",
|
||||
"| content=French and Mediterranean cuisine. Has seating to accommodate large groups.\n",
|
||||
"}}\n",
|
||||
"* {{listing | type=eat\n",
|
||||
"| name=Aroy-D | alt= | url=https://www.aroy-d.nl | email=\n",
|
||||
"| address=Noorderhagen 20 | lat=52.221786 | long=6.894911 | directions=\n",
|
||||
"| phone=+31 53 2302090 | tollfree=\n",
|
||||
"| hours=Th-Sa 17:00-22:00; Su W 17:00-21:00; closed M Tu | price=Entrees start at €14.50\n",
|
||||
"| lastedit=2020-06-02\n",
|
||||
"| content=Thai restaurant with traditional southeast Asia dishes.\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"===Splurge===\n",
|
||||
"* {{listing | type=eat\n",
|
||||
"| name=Bagels & Beans | alt= | url=http://bagelsbeans.nl | email=\n",
|
||||
"| address=Korte Hengelosestraat 33, 7511 JA | lat=52.222265 | long=6.892709 | directions=\n",
|
||||
"| phone=+31 53 436 4830 | tollfree=\n",
|
||||
"| hours= | price=\n",
|
||||
"| lastedit=2019-12-21\n",
|
||||
"| content=A high-end bagel shop and cafe\n",
|
||||
"}}\n",
|
||||
"* {{eat\n",
|
||||
"| name=Yuen's Oriëntal | alt= | url=http://www.yuencooking.nl | email=info@yuencooking.nl\n",
|
||||
"| address=Noorderhagen 9 | lat= | long= | directions=\n",
|
||||
"| phone=+31 53 434-6042 | tollfree= | fax=\n",
|
||||
"| hours=Th-Sa 17:00-22:00 | price=€35\n",
|
||||
"| content=Asian fusion restaurant with Japanese, Chinese, Thai, Korean and Vietnamese dishes all mixed up into one delicious treat. Try the surprise menu, it's always something new and always something good. Or the hotpot for a safe choice, with a bottle of wine.\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"==Drink==\n",
|
||||
"As Enschede has a university and is the second largest town in the eastern Netherlands it has a fair number of bars and cafes. The Oude Markt is the location of many bars and bistros which have large terraces, making it the perfect location for a drink on a warm summer night. The oldest pub in Enschede is Het Bolwerk, in the Bolwerkstraat, where one can drink a wide variety of beers and spirits, with the shocking exception of the Enschede-brewed Grolsch.\n",
|
||||
"\n",
|
||||
"While the hipper crowds turn to places like '''Aspen Valley''' and '''Central Park''', the alternative scene spends their Saturday night in '''Atak''', where one can see a wide variety of customers from hippies to gothics to hard-core-metal-lovers. The student associations also have their own pub in de Pakkerij, like the largest student association of Enschede, [http://www.aegee-enschede.nl AEGEE-Enschede] which is often also open to visits from AEGEE students visiting from other European student cities.\n",
|
||||
"* {{listing | type=drink\n",
|
||||
"| name=Brewskovitch Stadsbrouwerij | alt= | url=https://www.stanislausbrewskovitch.nl/ | email=info@stanislausbrewskovitch.nl\n",
|
||||
"| address=Stadsgravenstraat 59, 7511 ER | lat=52.221301 | long=6.896740 | directions=\n",
|
||||
"| phone=+31 53 203 2470 | tollfree=\n",
|
||||
"| hours= | price=\n",
|
||||
"| lastedit=2019-12-24\n",
|
||||
"| content=Pub with microbrewery.\n",
|
||||
"}}\n",
|
||||
"* {{drink\n",
|
||||
"| name=Paddy's | alt= | url=http://paddys.nl | email=info@paddys.nl\n",
|
||||
"| address=Oude Markt 12, 7511 GA | lat=52.221269 | long=6.895531 | directions=\n",
|
||||
"| phone=+31 53 432 8088 | tollfree=\n",
|
||||
"| hours=Th noon-midnight, F Sa noon-3AM, Su noon-23:00 | price=\n",
|
||||
"| lastedit=2022-07-31\n",
|
||||
"| content=Intimate pub.\n",
|
||||
"}}\n",
|
||||
"* {{drink\n",
|
||||
"| name=De Beiaard | alt= | url=http://beiaardenschede.nl | email=\n",
|
||||
"| address=Oude Markt 24 | lat=52.220558 | long=6.89666 | directions=\n",
|
||||
"| phone=+31 53 4306267 | tollfree=\n",
|
||||
"| hours= | price=\n",
|
||||
"| lastedit=2020-06-02\n",
|
||||
"| content=Local pub with ample of outdoor and indoor seating. Also has burgers, hot food and fish stew.\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"==Sleep==\n",
|
||||
"* {{sleep\n",
|
||||
"| name=IntercityHotel Enschede | alt= | url=https://www.intercityhotel.com/en/hotels/all-hotels/netherlands/enschede/intercityhotel-enschede | email=enschede@intercityhotel.com\n",
|
||||
"| address=Willem Wilminkplein 5 | lat=52.2227 | long=6.8935 | directions=exit Enschede railway station and head east for 2 minutes along Stationsplein, hotel is straight ahead at the end of Stationsplein\n",
|
||||
"| phone=+31 532 070 000 | tollfree=\n",
|
||||
"| checkin=noon | checkout=14:00 | price=€86\n",
|
||||
"| lastedit=2022-07-31\n",
|
||||
"| content=\n",
|
||||
"}}\n",
|
||||
"* {{sleep\n",
|
||||
"| name=ITC International Hotel | alt= | url=https://www.itc.nl/Pub/ITC-International-Hotel/General-Information.html | email=hotel-itc@utwente.nl\n",
|
||||
"| address=Boulevard 1945 4 | lat=52.21775 | long=6.89120 | directions=\n",
|
||||
"| phone=+31 534 803 999 | tollfree=+31 534 803 999 | fax=\n",
|
||||
"| checkin= | checkout= | price=\n",
|
||||
"| lastedit=2017-08-25\n",
|
||||
"| content=\n",
|
||||
"}}\n",
|
||||
"* {{sleep\n",
|
||||
"| name=Van der Valk Enschede | alt= | url=https://www.vandervalkhotelenschede.nl/ | email=\n",
|
||||
"| address=Zuiderval 140 | lat=52.2035 | long=6.8894 | directions=\n",
|
||||
"| phone=+31 538 000 800 | tollfree= | fax=\n",
|
||||
"| checkin= | checkout= | price=€82\n",
|
||||
"| lastedit=2017-08-25\n",
|
||||
"| content=\n",
|
||||
"}}\n",
|
||||
"* {{listing | type=sleep\n",
|
||||
"| name=U Parkhotel | alt= | url=http://uparkhotel.nl | email=\n",
|
||||
"| address=De Veldmaat 8, 7522 NM | lat=52.243046 | long=6.855474 | directions=in the University of Twente campus\n",
|
||||
"| phone=+31 53 433 1366 | tollfree=\n",
|
||||
"| checkin= | checkout= | price=\n",
|
||||
"| lastedit=2019-09-02\n",
|
||||
"| content= \n",
|
||||
"}}\n",
|
||||
"* {{listing | type=sleep\n",
|
||||
"| name=Fletcher Hotel-Restaurant De Broeierd-Enschede | alt= | url=http://fletcherhotelenschede.nl | email=\n",
|
||||
"| address=Hengelosestraat 725, 7521 PA | lat=52.239996 | long=6.843608 | directions=\n",
|
||||
"| phone=+31 53 850 6500 | tollfree=\n",
|
||||
"| checkin= | checkout= | price=\n",
|
||||
"| lastedit=2019-09-02\n",
|
||||
"| content=\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"== Connect ==\n",
|
||||
"As of July 2022, Enschede has 4G from KPN and Vodafone, and 5G from T-Mobile.\n",
|
||||
"\n",
|
||||
"The city provides free WiFi network \"Enschede_Stad_Van_Nu\" at the Oude Markt, the Van Heekplein, the Langestraat and on University of Twente campus.\n",
|
||||
"\n",
|
||||
"==Go next==\n",
|
||||
"\n",
|
||||
"Enschede is a city surrounded by nature. It is a good base for walking, cycling or horse riding trips.\n",
|
||||
"\n",
|
||||
"===Netherlands===\n",
|
||||
"* [[Deventer]], a city 62 km from Enschede, has a charming medieval city center\n",
|
||||
"* [[Dinkelland]] is a municipality containing the picturesque town of '''Ootmarsum''' and the medieval monastery '''Het Stift'''\n",
|
||||
"* [[Tubbergen]] is a municipality that contains the nature reserve '''Het Springendaal''', which is particularly beautiful during the autumn\n",
|
||||
"* [[Hof van Twente]] is a municipality known for its castles and mansions, the most famous being '''Castle Twickel''', in the village of '''Delden'''\n",
|
||||
"* [[Oldenzaal]] is a city that annually celebrates most famous Carnival from Twente\n",
|
||||
"\n",
|
||||
"===Germany===\n",
|
||||
"* [[Münster]], a city 68 km from Enschede, has various historical buildings and a large student population\n",
|
||||
"* [[Bad Bentheim]] is a town equally famous for its medieval castle and its hot springs\n",
|
||||
"* [[Tecklenburg]], 78 km from Enschede, is a charming and picturesque town in the hills. Difficult to get in without a car\n",
|
||||
"\n",
|
||||
"{{routebox\n",
|
||||
"| image1=NL-A1.png\n",
|
||||
"| imagesize1=22\n",
|
||||
"| directionl1=W\n",
|
||||
"| majorl1=[[Amsterdam]]\n",
|
||||
"| minorl1=[[Deventer]]\n",
|
||||
"| directionr1=E\n",
|
||||
"| majorr1=END\n",
|
||||
"| minorr1=\n",
|
||||
"}}\n",
|
||||
"\n",
|
||||
"{{geo|52.220000|6.896389}}\n",
|
||||
"{{isPartOf|Overijssel}}\n",
|
||||
"{{usablecity}}\n",
|
||||
"\"\"\"\n",
|
||||
"#endregion"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import mwparserfromhell as mwp\n",
|
||||
"import mwparserfromhell.nodes as nodes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Doc structure\n",
|
||||
"\n",
|
||||
"* curr: the element being worked on\n",
|
||||
"* parent: one level up\n",
|
||||
"* root: reference to the root\n",
|
||||
"\n",
|
||||
"each has a type, properties and children (tree)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Parsing Text\n",
|
||||
"Will start or continue a text block, a text block will be markdown and support (and parsed into) from stylistic tags, internal (wiki) and external links and text"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Stylistic Tags\n",
|
||||
"\n",
|
||||
"i.e. Bold, Italics, etc. We expressly don't support very weird edge cases like `test **test2 *test3 test4** test5*` for now (we could run an analysis if they even exist for giggles i guess)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"init_root = {\n",
|
||||
" 'type': \"root\",\n",
|
||||
" 'properties': {},\n",
|
||||
" 'children': [],\n",
|
||||
"}\n",
|
||||
"init_curr = root\n",
|
||||
"init_parent = None\n",
|
||||
"\n",
|
||||
"def parse(doc: mwp.wikicode.Wikicode, root=init_root, curr=init_curr, parent=init_parent):\n",
|
||||
" # if curr: goes level deeper, else could for example make a sibling\n",
|
||||
" def new_el(for_parent, type):\n",
|
||||
" parent = for_parent\n",
|
||||
" curr = {\n",
|
||||
" 'type': type,\n",
|
||||
" 'properties': {},\n",
|
||||
" 'children': []\n",
|
||||
" }\n",
|
||||
" parent['children'].append(curr)\n",
|
||||
" \n",
|
||||
" def handle_txt(node: nodes.text.Text):\n",
|
||||
" val: str = node.value\n",
|
||||
" if curr['type'] == 'text':\n",
|
||||
" # already working on a text, append\n",
|
||||
" curr['properties']['value'] = curr['properties']['value'] + val\n",
|
||||
" else:\n",
|
||||
" # not yet at a text\n",
|
||||
" new_el('text')\n",
|
||||
" curr['properties']['value'] = val\n",
|
||||
"\n",
|
||||
" def handle_tag(node: nodes.tag.Tag):\n",
|
||||
" # assert that we are in a text element (should the text start with a tag)\n",
|
||||
" if curr['type'] != \"text\":\n",
|
||||
" handle_txt(nodes.text.Text(\"\"))\n",
|
||||
" \n",
|
||||
" wrap = \"\"\n",
|
||||
" match node.tag:\n",
|
||||
" case 'b':\n",
|
||||
" wrap = \"**\"\n",
|
||||
" case _:\n",
|
||||
" print(node.tag)\n",
|
||||
" exit()\n",
|
||||
" \n",
|
||||
" # start the wrapper tag, recursively parse the children\n",
|
||||
" curr['properties']['value'] += wrap\n",
|
||||
" parse(node.contents, root, curr, parent)\n",
|
||||
" if curr['type'] != \"text\":\n",
|
||||
" print(\"panic, not in text after recursive parsing\")\n",
|
||||
" exit()\n",
|
||||
" curr['properties']['value'] += wrap\n",
|
||||
" \n",
|
||||
" def handle_heading(node: nodes.heading.Heading):\n",
|
||||
" # find level of last heading of equal greatness, make sibling\n",
|
||||
" new_el()\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.8"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
382
transform-documents.py
Normal file
382
transform-documents.py
Normal file
@ -0,0 +1,382 @@
|
||||
import mwparserfromhell as mwp
|
||||
import mwparserfromhell.nodes as nodes
|
||||
import re
|
||||
import json
|
||||
from typing import Dict, List, Any, Optional, Union, Tuple
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
class WikivoyageParser:
|
||||
def __init__(self):
|
||||
self.document_templates = [
|
||||
"pagebanner", "mapframe", "routebox", "geo", "isPartOf",
|
||||
"usablecity", "guidecity", "outlinecity"
|
||||
]
|
||||
self.listing_templates = ["see", "do", "buy", "eat", "drink", "sleep", "listing"]
|
||||
self.root = {
|
||||
"type": "root",
|
||||
"properties": {},
|
||||
"children": []
|
||||
}
|
||||
self.current_section = self.root
|
||||
|
||||
def parse(self, wikitext: str) -> Dict:
|
||||
"""Parse wikitext and return structured JSON tree"""
|
||||
self.root = {
|
||||
"type": "root",
|
||||
"properties": {},
|
||||
"children": []
|
||||
}
|
||||
self.current_section = self.root
|
||||
|
||||
# Parse the wikitext
|
||||
parsed = mwp.parse(wikitext)
|
||||
|
||||
# Process the parsed content
|
||||
self._process_nodes(parsed)
|
||||
|
||||
return self.root
|
||||
|
||||
def _process_nodes(self, wikicode):
|
||||
"""Process all nodes in the wikicode"""
|
||||
current_text = ""
|
||||
|
||||
for node in wikicode.nodes:
|
||||
# Handle different node types
|
||||
if isinstance(node, nodes.heading.Heading):
|
||||
# First flush any pending text
|
||||
if current_text:
|
||||
self._add_text_node(current_text)
|
||||
current_text = ""
|
||||
|
||||
# Create new section
|
||||
self._handle_heading(node)
|
||||
|
||||
elif isinstance(node, nodes.template.Template):
|
||||
# First flush any pending text
|
||||
if current_text:
|
||||
self._add_text_node(current_text)
|
||||
current_text = ""
|
||||
|
||||
# Handle template
|
||||
self._handle_template(node)
|
||||
|
||||
elif isinstance(node, nodes.text.Text):
|
||||
# Accumulate text
|
||||
current_text += str(node.value)
|
||||
|
||||
elif isinstance(node, nodes.tag.Tag):
|
||||
# Handle tag (potential styling)
|
||||
tag_text = self._convert_tag_to_markdown(node)
|
||||
current_text += tag_text
|
||||
|
||||
elif isinstance(node, nodes.wikilink.Wikilink):
|
||||
# Handle wikilink
|
||||
link_text = self._convert_wikilink_to_markdown(node)
|
||||
current_text += link_text
|
||||
|
||||
elif isinstance(node, nodes.external_link.ExternalLink):
|
||||
# Handle external link
|
||||
link_text = self._convert_external_link_to_markdown(node)
|
||||
current_text += link_text
|
||||
|
||||
elif isinstance(node, nodes.comment.Comment):
|
||||
# Skip comments
|
||||
pass
|
||||
|
||||
else:
|
||||
# Process other nodes as text
|
||||
current_text += str(node)
|
||||
|
||||
# Add any remaining text
|
||||
if current_text:
|
||||
self._add_text_node(current_text)
|
||||
|
||||
def _add_text_node(self, text: str):
|
||||
"""Add a text node to the current section"""
|
||||
# Avoid adding empty text nodes
|
||||
if not text.strip():
|
||||
return
|
||||
|
||||
text_node = {
|
||||
"type": "text",
|
||||
"properties": {
|
||||
"markdown": text.strip()
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
|
||||
self.current_section["children"].append(text_node)
|
||||
|
||||
def _handle_heading(self, heading_node):
|
||||
"""Handle a heading node by creating a new section"""
|
||||
level = heading_node.level
|
||||
title = str(heading_node.title).strip()
|
||||
|
||||
# Create new section node
|
||||
section = {
|
||||
"type": "section",
|
||||
"properties": {
|
||||
"title": title,
|
||||
"level": level
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
|
||||
# Find the appropriate parent section based on level
|
||||
parent = self.root
|
||||
|
||||
# If the level is 1, the parent is the root
|
||||
if level > 1:
|
||||
# Start from root and traverse the tree
|
||||
current = self.root
|
||||
current_level = 0
|
||||
|
||||
for child in reversed(self._get_all_sections()):
|
||||
child_level = child["properties"]["level"]
|
||||
if child_level < level:
|
||||
parent = child
|
||||
break
|
||||
|
||||
# Add the section to its parent
|
||||
parent["children"].append(section)
|
||||
|
||||
# Update current section
|
||||
self.current_section = section
|
||||
|
||||
def _get_all_sections(self):
|
||||
"""Get all sections in the document in the order they appear"""
|
||||
sections = []
|
||||
|
||||
def collect_sections(node):
|
||||
if node["type"] == "section":
|
||||
sections.append(node)
|
||||
for child in node["children"]:
|
||||
if child["type"] == "section":
|
||||
collect_sections(child)
|
||||
|
||||
collect_sections(self.root)
|
||||
return sections
|
||||
|
||||
def _handle_template(self, template_node):
|
||||
"""Handle a template node"""
|
||||
template_name = str(template_node.name).strip().lower()
|
||||
|
||||
# Check if it's a document-wide template
|
||||
if template_name in self.document_templates:
|
||||
self._handle_document_template(template_node)
|
||||
return
|
||||
|
||||
# Check if it's a listing template
|
||||
if template_name in self.listing_templates:
|
||||
self._handle_listing_template(template_node)
|
||||
return
|
||||
|
||||
# Handle other templates as regular nodes
|
||||
self._handle_other_template(template_node)
|
||||
|
||||
def _handle_document_template(self, template_node):
|
||||
"""Handle document-wide templates by adding to root properties"""
|
||||
template_name = str(template_node.name).strip().lower()
|
||||
|
||||
# Extract parameters
|
||||
params = {}
|
||||
for param in template_node.params:
|
||||
name = str(param.name).strip()
|
||||
value = str(param.value).strip()
|
||||
params[name] = value
|
||||
|
||||
# Add to root properties
|
||||
if template_name not in self.root["properties"]:
|
||||
self.root["properties"][template_name] = {}
|
||||
|
||||
self.root["properties"][template_name] = params
|
||||
|
||||
def _handle_listing_template(self, template_node):
|
||||
"""Handle listing templates (see, do, buy, eat, drink, sleep)"""
|
||||
template_name = str(template_node.name).strip().lower()
|
||||
|
||||
# Extract parameters
|
||||
properties = {}
|
||||
for param in template_node.params:
|
||||
name = str(param.name).strip()
|
||||
value = str(param.value).strip()
|
||||
|
||||
# Convert content to markdown if it's in the 'content' parameter
|
||||
if name == "content":
|
||||
value = self._convert_wikicode_to_markdown(param.value)
|
||||
|
||||
properties[name] = value
|
||||
|
||||
# Create listing node
|
||||
listing_node = {
|
||||
"type": template_name,
|
||||
"properties": properties,
|
||||
"children": []
|
||||
}
|
||||
|
||||
# Add to current section
|
||||
self.current_section["children"].append(listing_node)
|
||||
|
||||
def _handle_other_template(self, template_node):
|
||||
"""Handle other templates as general template nodes"""
|
||||
template_name = str(template_node.name).strip().lower()
|
||||
|
||||
# Extract parameters
|
||||
properties = {
|
||||
"name": template_name,
|
||||
"params": {}
|
||||
}
|
||||
|
||||
for param in template_node.params:
|
||||
name = str(param.name).strip()
|
||||
value = str(param.value).strip()
|
||||
properties["params"][name] = value
|
||||
|
||||
# Create template node
|
||||
template_node = {
|
||||
"type": "template",
|
||||
"properties": properties,
|
||||
"children": []
|
||||
}
|
||||
|
||||
# Add to current section
|
||||
self.current_section["children"].append(template_node)
|
||||
|
||||
def _convert_wikicode_to_markdown(self, wikicode) -> str:
|
||||
"""Convert wikicode to markdown"""
|
||||
markdown = ""
|
||||
|
||||
for node in wikicode.nodes:
|
||||
if isinstance(node, nodes.text.Text):
|
||||
markdown += str(node.value)
|
||||
|
||||
elif isinstance(node, nodes.tag.Tag):
|
||||
markdown += self._convert_tag_to_markdown(node)
|
||||
|
||||
elif isinstance(node, nodes.wikilink.Wikilink):
|
||||
markdown += self._convert_wikilink_to_markdown(node)
|
||||
|
||||
elif isinstance(node, nodes.external_link.ExternalLink):
|
||||
markdown += self._convert_external_link_to_markdown(node)
|
||||
|
||||
else:
|
||||
# For other nodes, just use their string representation
|
||||
markdown += str(node)
|
||||
|
||||
return markdown.strip()
|
||||
|
||||
def _convert_tag_to_markdown(self, tag_node) -> str:
|
||||
"""Convert HTML tag to markdown"""
|
||||
tag = str(tag_node.tag).lower()
|
||||
content = str(tag_node.contents)
|
||||
|
||||
# Convert the content recursively to handle nested tags
|
||||
if tag_node.contents:
|
||||
content = self._convert_wikicode_to_markdown(tag_node.contents)
|
||||
|
||||
# Handle different tags
|
||||
if tag == 'b' or tag == 'strong':
|
||||
return f"**{content}**"
|
||||
elif tag == 'i' or tag == 'em':
|
||||
return f"*{content}*"
|
||||
elif tag == 'u':
|
||||
return f"_{content}_"
|
||||
elif tag == 'strike' or tag == 's' or tag == 'del':
|
||||
return f"~~{content}~~"
|
||||
elif tag == 'code':
|
||||
return f"`{content}`"
|
||||
elif tag == 'pre':
|
||||
return f"```\n{content}\n```"
|
||||
elif tag == 'br':
|
||||
return "\n"
|
||||
elif tag == 'hr':
|
||||
return "\n---\n"
|
||||
elif tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
|
||||
level = int(tag[1])
|
||||
return f"\n{'#' * level} {content}\n"
|
||||
elif tag == 'a':
|
||||
href = ""
|
||||
for attr in tag_node.attributes:
|
||||
if str(attr.name).lower() == 'href':
|
||||
href = str(attr.value)
|
||||
break
|
||||
return f"[{content}]({href})"
|
||||
elif tag == 'img':
|
||||
src = alt = ""
|
||||
for attr in tag_node.attributes:
|
||||
if str(attr.name).lower() == 'src':
|
||||
src = str(attr.value)
|
||||
elif str(attr.name).lower() == 'alt':
|
||||
alt = str(attr.value)
|
||||
return f""
|
||||
else:
|
||||
# For unknown tags, just return the content
|
||||
return content
|
||||
|
||||
def _convert_wikilink_to_markdown(self, wikilink_node) -> str:
|
||||
"""Convert wikilink to markdown"""
|
||||
title = str(wikilink_node.title)
|
||||
|
||||
if wikilink_node.text:
|
||||
text = str(wikilink_node.text)
|
||||
return f"[{text}]({title})"
|
||||
else:
|
||||
return f"[{title}]({title})"
|
||||
|
||||
def _convert_external_link_to_markdown(self, link_node) -> str:
|
||||
"""Convert external link to markdown"""
|
||||
url = str(link_node.url)
|
||||
|
||||
if link_node.title:
|
||||
title = str(link_node.title)
|
||||
return f"[{title}]({url})"
|
||||
else:
|
||||
return url
|
||||
|
||||
def export_json(self, root=None, indent=2) -> str:
|
||||
"""Export the tree as JSON string"""
|
||||
if root is None:
|
||||
root = self.root
|
||||
|
||||
return json.dumps(root, indent=indent)
|
||||
|
||||
def process_file(input_file: Path, parser: WikivoyageParser) -> None:
|
||||
"""Process a single wiki file and save JSON output"""
|
||||
# Create output path with .json extension
|
||||
output_file = input_file.with_suffix('.json')
|
||||
|
||||
# Ensure output directory exists
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
# Read and parse input file
|
||||
with open(input_file, 'r', encoding='utf-8') as f:
|
||||
wikitext = f.read()
|
||||
|
||||
result = parser.parse(wikitext)
|
||||
|
||||
# Write JSON output
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(parser.export_json())
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing {input_file}: {e}")
|
||||
|
||||
def main():
|
||||
# Initialize parser once for reuse
|
||||
parser = WikivoyageParser()
|
||||
|
||||
# Get input directory from command line or use current directory
|
||||
input_dir = Path(sys.argv[1] if len(sys.argv) > 1 else '.')
|
||||
|
||||
# Process all .txt files recursively
|
||||
for txt_file in input_dir.rglob('*.txt'):
|
||||
print(f"Processing {txt_file}")
|
||||
process_file(txt_file, parser)
|
||||
|
||||
print("Processing complete")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
253
types.ts
Normal file
253
types.ts
Normal file
@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Enum for all possible node types
|
||||
*/
|
||||
export enum NodeType {
|
||||
Root = "root",
|
||||
Section = "section",
|
||||
Text = "text",
|
||||
Template = "template",
|
||||
See = "see",
|
||||
Do = "do",
|
||||
Buy = "buy",
|
||||
Eat = "eat",
|
||||
Drink = "drink",
|
||||
Sleep = "sleep",
|
||||
Listing = "listing",
|
||||
Marker = "marker",
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for all node types in the Wikivoyage tree
|
||||
*/
|
||||
export interface BaseNode {
|
||||
/** Type identifier for the node */
|
||||
type: NodeType;
|
||||
/** Properties specific to this node type */
|
||||
properties: Record<string, any>;
|
||||
/** Child nodes */
|
||||
children: WikiNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Root node of the document
|
||||
*/
|
||||
export interface RootNode extends BaseNode {
|
||||
type: NodeType.Root;
|
||||
properties: {
|
||||
/** Page banner information */
|
||||
pagebanner?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
/** Map frame information */
|
||||
mapframe?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
/** Route box information */
|
||||
routebox?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
/** Geographic coordinates */
|
||||
geo?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
/** Parent region information */
|
||||
isPartOf?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
/** City status information */
|
||||
usablecity?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
guidecity?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
outlinecity?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
title: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Section node representing a heading and its content
|
||||
*/
|
||||
export interface SectionNode extends BaseNode {
|
||||
type: NodeType.Section;
|
||||
properties: {
|
||||
/** Section title */
|
||||
title: string;
|
||||
/** Heading level (1-6) */
|
||||
level: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Text node containing markdown content
|
||||
*/
|
||||
export interface TextNode extends BaseNode {
|
||||
type: NodeType.Text;
|
||||
properties: {
|
||||
/** Markdown formatted text */
|
||||
markdown: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic template node for templates not specifically handled
|
||||
*/
|
||||
export interface TemplateNode extends BaseNode {
|
||||
type: NodeType.Template;
|
||||
properties: {
|
||||
/** Template name */
|
||||
name: string;
|
||||
/** Template parameters */
|
||||
params: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for listing templates (see, do, buy, eat, drink, sleep)
|
||||
*/
|
||||
export interface ListingNode extends BaseNode {
|
||||
properties: {
|
||||
/** Name of the listing */
|
||||
name: string;
|
||||
/** Alternative name */
|
||||
alt?: string;
|
||||
/** URL for the listing */
|
||||
url?: string;
|
||||
/** Email address */
|
||||
email?: string;
|
||||
/** Physical address */
|
||||
address?: string;
|
||||
/** Latitude coordinate */
|
||||
lat?: string;
|
||||
/** Longitude coordinate */
|
||||
long?: string;
|
||||
/** Directions to reach the location */
|
||||
directions?: string;
|
||||
/** Phone number */
|
||||
phone?: string;
|
||||
/** Toll-free phone number */
|
||||
tollfree?: string;
|
||||
/** Fax number */
|
||||
fax?: string;
|
||||
/** Opening hours */
|
||||
hours?: string;
|
||||
/** Price information */
|
||||
price?: string;
|
||||
/** Last edit timestamp */
|
||||
lastedit?: string;
|
||||
/** Wikipedia article name */
|
||||
wikipedia?: string;
|
||||
/** Wikidata ID */
|
||||
wikidata?: string;
|
||||
/** Image filename */
|
||||
image?: string;
|
||||
/** Description content */
|
||||
content?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* See listing (attractions, landmarks)
|
||||
*/
|
||||
export interface SeeNode extends ListingNode {
|
||||
type: NodeType.See;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do listing (activities)
|
||||
*/
|
||||
export interface DoNode extends ListingNode {
|
||||
type: NodeType.Do;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buy listing (shopping)
|
||||
*/
|
||||
export interface BuyNode extends ListingNode {
|
||||
type: NodeType.Buy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eat listing (restaurants)
|
||||
*/
|
||||
export interface EatNode extends ListingNode {
|
||||
type: NodeType.Eat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drink listing (bars, cafes)
|
||||
*/
|
||||
export interface DrinkNode extends ListingNode {
|
||||
type: NodeType.Drink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep listing (accommodations)
|
||||
*/
|
||||
export interface SleepNode extends ListingNode {
|
||||
type: NodeType.Sleep;
|
||||
properties: ListingNode["properties"] & {
|
||||
/** Check-in time */
|
||||
checkin?: string;
|
||||
/** Check-out time */
|
||||
checkout?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic listing node
|
||||
*/
|
||||
export interface GenericListingNode extends ListingNode {
|
||||
type: NodeType.Listing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker node for map locations
|
||||
*/
|
||||
export interface MarkerNode extends BaseNode {
|
||||
type: NodeType.Marker;
|
||||
properties: {
|
||||
/** Type of marker */
|
||||
type: string;
|
||||
/** Name of the location */
|
||||
name: string;
|
||||
/** Latitude coordinate */
|
||||
lat: string;
|
||||
/** Longitude coordinate */
|
||||
long: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type of all possible node types
|
||||
*/
|
||||
export type WikiNode<T extends NodeType = NodeType> = T extends NodeType.Root
|
||||
? RootNode
|
||||
: T extends NodeType.Section
|
||||
? SectionNode
|
||||
: T extends NodeType.Text
|
||||
? TextNode
|
||||
: T extends NodeType.Template
|
||||
? TemplateNode
|
||||
: T extends NodeType.See
|
||||
? SeeNode
|
||||
: T extends NodeType.Do
|
||||
? DoNode
|
||||
: T extends NodeType.Buy
|
||||
? BuyNode
|
||||
: T extends NodeType.Eat
|
||||
? EatNode
|
||||
: T extends NodeType.Drink
|
||||
? DrinkNode
|
||||
: T extends NodeType.Sleep
|
||||
? SleepNode
|
||||
: T extends NodeType.Listing
|
||||
? GenericListingNode
|
||||
: T extends NodeType.Marker
|
||||
? MarkerNode
|
||||
: never;
|
90
uv.lock
generated
Normal file
90
uv.lock
generated
Normal file
@ -0,0 +1,90 @@
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "mapvoyage-extract"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "mwparserfromhell" },
|
||||
{ name = "wikitextparser" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "mwparserfromhell", specifier = ">=0.6.6" },
|
||||
{ name = "wikitextparser", specifier = ">=0.56.3" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mwparserfromhell"
|
||||
version = "0.6.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/aa/358f9af602b743ac8898353f240f678b69722801bd0625507c69d9755936/mwparserfromhell-0.6.6.tar.gz", hash = "sha256:71afec1e9784ba576e95d6f34845582d3c733a3a52ba770dd8a9c3a40e5b649f", size = 138899 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/61/c0/059513420490a2d96b470d4f560445d89f022a4749a20a952bfb002e2915/mwparserfromhell-0.6.6-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:910d36bc70e8bea758380e75c12fd47626b295abec9f73a6099d8f937a649e77", size = 124175 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/ab/da1b51303f0457c8cd343cf2d9a66fd0c5654fe8d0331c8107751459191f/mwparserfromhell-0.6.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2febd92a55a3f19b461833267726cb81429c3d6cb0006ad1691dfa849789e5d", size = 203036 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/39/6bd8d9678e8b3f57babee36b5a9540db8cccba1b81a64ed56576c653994b/mwparserfromhell-0.6.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b75fae6d01c8fda19dbf127175122d7aa2964ef6454690e6868bbc3d80a7bc1", size = 201886 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/e1/026ea60ac66a8bc9314230cc0c1ad2bf496ceb16a3771d4fcfd6ecc25971/mwparserfromhell-0.6.6-cp312-cp312-win32.whl", hash = "sha256:19e9a4bcd85707c83172405eb2a9a046eff9d38dd7f1a56a5e5ecbbfef4a640a", size = 98748 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/09/24c2f37524a3ebc3574975766748c7e4423ecefaa815c9fc4a324cbcf94a/mwparserfromhell-0.6.6-cp312-cp312-win_amd64.whl", hash = "sha256:cdc46c115b2495d4025920b7b30a6885a96d2b797ccc4009bf3cc02940ae55d3", size = 101071 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.11.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wikitextparser"
|
||||
version = "0.56.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "regex" },
|
||||
{ name = "wcwidth" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/68/8f/38ae3bb4d5b87a30f961c535365e807167ba7dc31b3bdc16c708fcd30153/wikitextparser-0.56.3.tar.gz", hash = "sha256:2fce8141975d15ba7bd04a7605792a28d7cf216ebce10287d086f32af051ed26", size = 73175 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/5f/3109173deefaaf4a4d8f4086b20367a42fd2f77d6a096db04e835aa5dfe2/wikitextparser-0.56.3-py3-none-any.whl", hash = "sha256:49bcbe421f0c126fba254a8f2e41262e679a2a88f2010dda90198a287616b5e4", size = 66284 },
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user